<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Home — Symbolic Software</title>
    <link>https://symbolic.software/</link>
    <description>Applied cryptography consultancy. Design-level security audits — protocol architecture, cryptographic design, threat modeling, formal verification. Since 2017.</description>
    <language>en-us</language>
    <managingEditor>symbolicsoft</managingEditor>
    <lastBuildDate>Sat, 09 May 2026 11:44:14 &#43;0000</lastBuildDate>
    
    <atom:link href="https://symbolic.software/index.xml" rel="self" type="application/rss+xml" />
    
    <image>
      <url>https://symbolic.software/images/icon.png</url>
      <title>Symbolic Software</title>
      <link>https://symbolic.software/</link>
    </image>
    
    <item>
      <title>hpke-ng: Faster, Smaller, Harder HPKE for Rust</title>
      <link>https://symbolic.software/blog/2026-05-08-hpke-ng/</link>
      <pubDate>Fri, 08 May 2026 00:00:00 &#43;0000</pubDate>
      <guid>https://symbolic.software/blog/2026-05-08-hpke-ng/</guid>
      <description>Across 62 head-to-head benchmarks against hpke-rs, hpke-ng wins 43 — including ML-KEM decap at 53–55% faster, X-Wing decap at 38%, X25519 decap at 41%, and every post-quantum encap/decap row a clean win — ties 14, ships a 30% smaller binary, and a type system that catches four classes of bug at compile time.</description>
      <content:encoded>&lt;p&gt;Today we&amp;rsquo;re releasing &lt;a href=&#34;https://github.com/symbolicsoft/hpke-ng&#34;&gt;hpke-ng&lt;/a&gt;, a clean-slate Rust implementation of &lt;a href=&#34;https://www.rfc-editor.org/rfc/rfc9180.html&#34;&gt;HPKE (RFC 9180)&lt;/a&gt;. It is published under Apache-2.0 OR MIT, and you can install it now with &lt;code&gt;cargo add hpke-ng&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Across 62 head-to-head benchmarks against &lt;a href=&#34;https://github.com/cryspen/hpke-rs&#34;&gt;hpke-rs&lt;/a&gt; — currently the most widely deployed Rust HPKE library — hpke-ng wins 43, ties 14, and loses 5, with the wins concentrating on the &lt;strong&gt;post-quantum decap path&lt;/strong&gt; (53–55% on ML-KEM-768 and ML-KEM-1024, the largest deltas in the entire dataset, plus a fresh 38% win on X-Wing decap from the same caching trick), the &lt;strong&gt;post-quantum encap path&lt;/strong&gt; (30–37% on ML-KEM, 14% on X-Wing), the &lt;strong&gt;classical KEM decap path&lt;/strong&gt; (41% on X25519 — caching the recipient&amp;rsquo;s public-key bytes alongside the secret eliminates a redundant base-point scalar multiplication on every call), the &lt;strong&gt;single-shot open path&lt;/strong&gt; (23–35% across payload sizes), and the &lt;strong&gt;post-quantum and ChaCha20 setup paths&lt;/strong&gt; (45–50% on ML-KEM receivers, 31% on X25519 receivers). Every PQ encap and decap row is a clean win.&lt;/p&gt;
&lt;div class=&#34;hpke-score reveal&#34;&gt;
&lt;div class=&#34;hpke-panel-bar&#34;&gt;
&lt;div class=&#34;left&#34;&gt;&lt;div class=&#34;traffic&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;span class=&#34;filename&#34;&gt;cargo bench · &lt;span class=&#34;stem&#34;&gt;comparative&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;span class=&#34;status&#34;&gt;&lt;span class=&#34;pulse&#34;&gt;&lt;/span&gt;62 head-to-head benchmarks&lt;/span&gt;
&lt;/div&gt;
&lt;div class=&#34;hpke-score-counters&#34;&gt;
&lt;div class=&#34;counter counter-win&#34;&gt;&lt;div class=&#34;big&#34;&gt;&lt;span data-counter-target=&#34;43&#34; data-counter-from=&#34;0&#34;&gt;43&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;label&#34;&gt;hpke-ng wins&lt;/div&gt;&lt;/div&gt;
&lt;div class=&#34;counter counter-tie&#34;&gt;&lt;div class=&#34;big&#34;&gt;&lt;span data-counter-target=&#34;14&#34; data-counter-from=&#34;0&#34;&gt;14&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;label&#34;&gt;tied&lt;/div&gt;&lt;/div&gt;
&lt;div class=&#34;counter counter-loss&#34;&gt;&lt;div class=&#34;big&#34;&gt;&lt;span data-counter-target=&#34;5&#34; data-counter-from=&#34;0&#34;&gt;5&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;label&#34;&gt;hpke-rs wins&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;hpke-score-dots&#34;&gt;
&lt;span class=&#34;hpke-score-dot win&#34; style=&#34;animation-delay:.00s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot win&#34; style=&#34;animation-delay:.02s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot win&#34; style=&#34;animation-delay:.04s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot win&#34; style=&#34;animation-delay:.06s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot win&#34; style=&#34;animation-delay:.08s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot win&#34; style=&#34;animation-delay:.10s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot win&#34; style=&#34;animation-delay:.12s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot win&#34; style=&#34;animation-delay:.14s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot win&#34; style=&#34;animation-delay:.16s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot win&#34; style=&#34;animation-delay:.18s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot win&#34; style=&#34;animation-delay:.20s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot win&#34; style=&#34;animation-delay:.22s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot win&#34; style=&#34;animation-delay:.24s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot win&#34; style=&#34;animation-delay:.26s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot win&#34; style=&#34;animation-delay:.28s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot win&#34; style=&#34;animation-delay:.30s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot win&#34; style=&#34;animation-delay:.32s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot win&#34; style=&#34;animation-delay:.34s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot win&#34; style=&#34;animation-delay:.36s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot win&#34; style=&#34;animation-delay:.38s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot win&#34; style=&#34;animation-delay:.40s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot win&#34; style=&#34;animation-delay:.42s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot win&#34; style=&#34;animation-delay:.44s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot win&#34; style=&#34;animation-delay:.46s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot win&#34; style=&#34;animation-delay:.48s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot win&#34; style=&#34;animation-delay:.50s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot win&#34; style=&#34;animation-delay:.52s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot win&#34; style=&#34;animation-delay:.54s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot win&#34; style=&#34;animation-delay:.56s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot win&#34; style=&#34;animation-delay:.58s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot win&#34; style=&#34;animation-delay:.60s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot win&#34; style=&#34;animation-delay:.62s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot win&#34; style=&#34;animation-delay:.64s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot win&#34; style=&#34;animation-delay:.66s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot win&#34; style=&#34;animation-delay:.68s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot win&#34; style=&#34;animation-delay:.70s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot win&#34; style=&#34;animation-delay:.72s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot win&#34; style=&#34;animation-delay:.74s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot win&#34; style=&#34;animation-delay:.76s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot win&#34; style=&#34;animation-delay:.78s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot win&#34; style=&#34;animation-delay:.80s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot win&#34; style=&#34;animation-delay:.82s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot win&#34; style=&#34;animation-delay:.84s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot tie&#34; style=&#34;animation-delay:.86s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot tie&#34; style=&#34;animation-delay:.88s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot tie&#34; style=&#34;animation-delay:.90s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot tie&#34; style=&#34;animation-delay:.92s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot tie&#34; style=&#34;animation-delay:.94s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot tie&#34; style=&#34;animation-delay:.96s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot tie&#34; style=&#34;animation-delay:.98s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot tie&#34; style=&#34;animation-delay:1.00s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot tie&#34; style=&#34;animation-delay:1.02s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot tie&#34; style=&#34;animation-delay:1.04s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot tie&#34; style=&#34;animation-delay:1.06s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot tie&#34; style=&#34;animation-delay:1.08s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot tie&#34; style=&#34;animation-delay:1.10s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot tie&#34; style=&#34;animation-delay:1.12s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot loss&#34; style=&#34;animation-delay:1.14s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot loss&#34; style=&#34;animation-delay:1.16s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot loss&#34; style=&#34;animation-delay:1.18s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot loss&#34; style=&#34;animation-delay:1.20s&#34;&gt;&lt;/span&gt;&lt;span class=&#34;hpke-score-dot loss&#34; style=&#34;animation-delay:1.22s&#34;&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;div class=&#34;hpke-score-legend&#34;&gt;
&lt;span&gt;&lt;span class=&#34;legend-dot win&#34;&gt;&lt;/span&gt;hpke-ng faster (&gt;3% delta)&lt;/span&gt;
&lt;span&gt;&lt;span class=&#34;legend-dot tie&#34;&gt;&lt;/span&gt;within criterion noise band (±3%)&lt;/span&gt;
&lt;span&gt;&lt;span class=&#34;legend-dot loss&#34;&gt;&lt;/span&gt;hpke-rs faster&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Both libraries call the same RustCrypto primitive crates underneath, so the cryptography itself is identical. Both pass the full RFC 9180 known-answer-test set. Both produce byte-identical wire output to each other on every ciphersuite we differentially tested. The wins are gains in framing, monomorphization, allocation behaviour, and dispatch — not in the underlying crypto math. That is the point: the math is a solved problem, and the surrounding library is where the engineering still has slack. The 32 ties tell you something too — for the AEAD-bound rows and for KEM operations where both libraries converge to the speed of the underlying primitive (single-key generation, ML-KEM IKM expansion, X-Wing key derivation), hpke-ng matches that ceiling without overhead.&lt;/p&gt;
&lt;h2 id=&#34;why-we-built-another-hpke-library&#34;&gt;Why we built another HPKE library&lt;/h2&gt;
&lt;p&gt;Earlier this year we found and reported two security bugs in hpke-rs. The first was a &lt;a href=&#34;https://github.com/cryspen/hpke-rs/pull/117&#34;&gt;missing RFC 9180 §7.1.4 zero-shared-secret check&lt;/a&gt;: a low-order or identity public key forces the X25519 shared secret to all zeros, after which the rest of the key schedule becomes deterministic and predictable to anyone who knows the static recipient key. The fix is a single comparison against zero, which RFC 9180 explicitly requires; it was missing. The second was a &lt;a href=&#34;https://github.com/cryspen/hpke-rs/pull/118&#34;&gt;&lt;code&gt;u32&lt;/code&gt; sequence-counter that silently wrapped in release builds&lt;/a&gt;, reusing nonces after 2³² messages. Nonce reuse in AEAD is catastrophic — for AES-GCM it leaks the authentication key; for ChaCha20-Poly1305 it leaks plaintext via XOR — and the wrap was happening below the type system, in a release build, with no diagnostic. Both are now fixed. We documented the broader pattern of bugs we kept finding in libraries marketed under &amp;ldquo;high assurance&amp;rdquo; branding &lt;a href=&#34;https://symbolic.software/blog/2026-02-05-cryspen/&#34;&gt;in February&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;That experience — together with the day-to-day frictions of integrating HPKE through a library that wasn&amp;rsquo;t built to make those bug classes structurally impossible — is what got us thinking about a rewrite. Three frictions in particular kept showing up.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The provider abstraction.&lt;/strong&gt; hpke-rs is structured as a generic library over an &lt;code&gt;HpkeCrypto&lt;/code&gt; trait, with two backend implementations — RustCrypto and libcrux — shipped as separate crates. The abstraction is real engineering and serves a real purpose: it lets a deployment swap the underlying crypto stack without touching call sites. The cost is that every primitive call goes through a trait dispatch, every &lt;code&gt;Hpke&lt;/code&gt; value carries a 320-byte instance struct (most of it a 256-byte ChaCha20 PRNG state), and the workspace is four crates instead of one.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The struct-owned PRNG.&lt;/strong&gt; &lt;code&gt;Hpke&amp;lt;Crypto&amp;gt;::new&lt;/code&gt; constructs and stores a PRNG. That PRNG is reused across operations, which is fine functionally but creates a subtle aliasing hazard: cloning an &lt;code&gt;Hpke&lt;/code&gt; does not clone the PRNG state — per the rustdoc — so a careless clone can reset randomness in ways that aren&amp;rsquo;t visible at the call site. This is the kind of footgun whose damage is invisible until the day it isn&amp;rsquo;t.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;Option&amp;lt;&amp;amp;[u8]&amp;gt;&lt;/code&gt; for required-by-mode arguments.&lt;/strong&gt; &lt;code&gt;hpke.seal(&amp;amp;pk, info, aad, pt, None, None, None)&lt;/code&gt; is the canonical Base-mode call. The three &lt;code&gt;None&lt;/code&gt;s are the PSK, PSK ID, and sender static key — all required in the Auth or AuthPsk modes. The single &lt;code&gt;seal&lt;/code&gt; method accepts every mode by making mode-specific arguments optional, which means the type system can&amp;rsquo;t tell you that you&amp;rsquo;ve built a Base-mode call with a PSK supplied.&lt;/p&gt;
&lt;p&gt;These are not catastrophic problems. They&amp;rsquo;re the kind of small persistent costs you stop noticing until you build the alternative.&lt;/p&gt;
&lt;h2 id=&#34;the-shape-change-enum-dispatch-becomes-type-state&#34;&gt;The shape change: enum dispatch becomes type-state&lt;/h2&gt;
&lt;p&gt;The single biggest design difference between hpke-ng and hpke-rs is what carries the ciphersuite. In hpke-rs, the ciphersuite is four runtime enums (&lt;code&gt;Mode&lt;/code&gt;, &lt;code&gt;KemAlgorithm&lt;/code&gt;, &lt;code&gt;KdfAlgorithm&lt;/code&gt;, &lt;code&gt;AeadAlgorithm&lt;/code&gt;) constructed at &lt;code&gt;Hpke::new&lt;/code&gt; time. In hpke-ng, the ciphersuite is the type itself — &lt;code&gt;Hpke&amp;lt;DhKemX25519HkdfSha256, HkdfSha256, ChaCha20Poly1305&amp;gt;&lt;/code&gt; — and the struct body is &lt;code&gt;PhantomData&amp;lt;(K, F, A)&amp;gt;&lt;/code&gt;. Zero bytes at runtime; everything resolved at the call site by the compiler.&lt;/p&gt;
&lt;div class=&#34;hpke-api reveal&#34;&gt;
&lt;div class=&#34;hpke-panel-bar&#34;&gt;
&lt;div class=&#34;left&#34;&gt;&lt;div class=&#34;traffic&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;span class=&#34;filename&#34;&gt;canonical seal · &lt;span class=&#34;stem&#34;&gt;&#34;encrypt one message&#34;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;hpke-api-grid&#34;&gt;
&lt;div class=&#34;hpke-api-col&#34;&gt;
&lt;div class=&#34;hpke-api-head&#34;&gt;&lt;span class=&#34;lib lib-rs&#34;&gt;hpke-rs&lt;/span&gt;&lt;span class=&#34;meta&#34;&gt;7 args · 3× &lt;code&gt;Option&amp;lt;&amp;amp;[u8]&amp;gt;&lt;/code&gt;&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&#34;hpke-api-code&#34;&gt;&lt;span class=&#34;ln&#34;&gt;&lt;span class=&#34;kw&#34;&gt;let mut&lt;/span&gt; hpke = Hpke::&amp;lt;HpkeRustCrypto&amp;gt;::&lt;span class=&#34;fn&#34;&gt;new&lt;/span&gt;(&lt;/span&gt;
&lt;span class=&#34;ln&#34;&gt;    Mode::Base,&lt;/span&gt;
&lt;span class=&#34;ln&#34;&gt;    KemAlgorithm::DhKem25519,&lt;/span&gt;
&lt;span class=&#34;ln&#34;&gt;    KdfAlgorithm::HkdfSha256,&lt;/span&gt;
&lt;span class=&#34;ln&#34;&gt;    AeadAlgorithm::ChaCha20Poly1305,&lt;/span&gt;
&lt;span class=&#34;ln&#34;&gt;);&lt;/span&gt;
&lt;span class=&#34;ln&#34;&gt;&lt;span class=&#34;kw&#34;&gt;let&lt;/span&gt; kp = hpke.&lt;span class=&#34;fn&#34;&gt;generate_key_pair&lt;/span&gt;()?;&lt;/span&gt;
&lt;span class=&#34;ln&#34;&gt;&lt;span class=&#34;kw&#34;&gt;let&lt;/span&gt; (sk, pk) = kp.&lt;span class=&#34;fn&#34;&gt;into_keys&lt;/span&gt;();&lt;/span&gt;
&lt;span class=&#34;ln&#34;&gt;&lt;span class=&#34;kw&#34;&gt;let&lt;/span&gt; (enc, ct) = hpke.&lt;span class=&#34;fn&#34;&gt;seal&lt;/span&gt;(&lt;/span&gt;
&lt;span class=&#34;ln&#34;&gt;    &amp;amp;pk, info, aad, pt,&lt;/span&gt;
&lt;span class=&#34;ln&#34;&gt;    &lt;span class=&#34;lit&#34;&gt;None&lt;/span&gt;, &lt;span class=&#34;lit&#34;&gt;None&lt;/span&gt;, &lt;span class=&#34;lit&#34;&gt;None&lt;/span&gt;, &lt;span class=&#34;com&#34;&gt;// psk, psk_id, sk_s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&#34;ln&#34;&gt;)?;&lt;/span&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div class=&#34;hpke-api-col&#34;&gt;
&lt;div class=&#34;hpke-api-head&#34;&gt;&lt;span class=&#34;lib lib-ng&#34;&gt;hpke-ng&lt;/span&gt;&lt;span class=&#34;meta&#34;&gt;5 args · zero placeholders&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&#34;hpke-api-code&#34;&gt;&lt;span class=&#34;ln&#34;&gt;&lt;span class=&#34;kw&#34;&gt;type&lt;/span&gt; Suite = Hpke&amp;lt;&lt;/span&gt;
&lt;span class=&#34;ln&#34;&gt;    DhKemX25519HkdfSha256,&lt;/span&gt;
&lt;span class=&#34;ln&#34;&gt;    HkdfSha256,&lt;/span&gt;
&lt;span class=&#34;ln&#34;&gt;    ChaCha20Poly1305,&lt;/span&gt;
&lt;span class=&#34;ln&#34;&gt;&amp;gt;;&lt;/span&gt;
&lt;span class=&#34;ln&#34;&gt;&lt;span class=&#34;kw&#34;&gt;let mut&lt;/span&gt; os = OsRng;&lt;/span&gt;
&lt;span class=&#34;ln&#34;&gt;&lt;span class=&#34;kw&#34;&gt;let mut&lt;/span&gt; rng = os.&lt;span class=&#34;fn&#34;&gt;unwrap_mut&lt;/span&gt;();&lt;/span&gt;
&lt;span class=&#34;ln&#34;&gt;&lt;span class=&#34;kw&#34;&gt;let&lt;/span&gt; (sk, pk) = DhKemX25519HkdfSha256::&lt;span class=&#34;fn&#34;&gt;generate&lt;/span&gt;(&amp;amp;&lt;span class=&#34;kw&#34;&gt;mut&lt;/span&gt; rng)?;&lt;/span&gt;
&lt;span class=&#34;ln&#34;&gt;&lt;span class=&#34;kw&#34;&gt;let&lt;/span&gt; (enc, ct) = Suite::&lt;span class=&#34;fn&#34;&gt;seal_base&lt;/span&gt;(&lt;/span&gt;
&lt;span class=&#34;ln&#34;&gt;    &amp;amp;&lt;span class=&#34;kw&#34;&gt;mut&lt;/span&gt; rng, &amp;amp;pk, info, aad, pt,&lt;/span&gt;
&lt;span class=&#34;ln&#34;&gt;)?;&lt;/span&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The visible consequence is the call site. The invisible consequence is what the compiler can rule out:&lt;/p&gt;
&lt;div class=&#34;hpke-reject reveal-stagger&#34;&gt;
&lt;div class=&#34;hpke-reject-head&#34;&gt;
&lt;span&gt;Operation&lt;/span&gt;&lt;span&gt;hpke-rs&lt;/span&gt;&lt;span&gt;hpke-ng&lt;/span&gt;
&lt;/div&gt;
&lt;div class=&#34;hpke-reject-row&#34;&gt;
&lt;div class=&#34;op&#34;&gt;&lt;code&gt;Hpke::&amp;lt;XWingDraft06, _, _&amp;gt;::seal_auth(...)&lt;/code&gt;&lt;/div&gt;
&lt;div class=&#34;rs&#34;&gt;&lt;span class=&#34;badge badge-runtime&#34;&gt;runtime&lt;/span&gt; &lt;code&gt;Error::UnsupportedKemOperation&lt;/code&gt;&lt;/div&gt;
&lt;div class=&#34;ng&#34;&gt;&lt;span class=&#34;badge badge-compile&#34;&gt;compile&lt;/span&gt; trait bound &lt;code&gt;K: AuthKem&lt;/code&gt; not satisfied&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;hpke-reject-row&#34;&gt;
&lt;div class=&#34;op&#34;&gt;&lt;code&gt;Hpke::&amp;lt;_, _, ExportOnly&amp;gt;::seal_base(...)&lt;/code&gt;&lt;/div&gt;
&lt;div class=&#34;rs&#34;&gt;&lt;span class=&#34;badge badge-runtime&#34;&gt;runtime&lt;/span&gt; &lt;code&gt;HpkeError::InvalidConfig&lt;/code&gt;&lt;/div&gt;
&lt;div class=&#34;ng&#34;&gt;&lt;span class=&#34;badge badge-compile&#34;&gt;compile&lt;/span&gt; no &lt;code&gt;seal_base&lt;/code&gt; on &lt;code&gt;ExportOnly&lt;/code&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;hpke-reject-row&#34;&gt;
&lt;div class=&#34;op&#34;&gt;Wrong &lt;code&gt;KemAlgorithm&lt;/code&gt; for a private key&lt;/div&gt;
&lt;div class=&#34;rs&#34;&gt;&lt;span class=&#34;badge badge-runtime&#34;&gt;runtime&lt;/span&gt; mismatch error at &lt;code&gt;setup_*&lt;/code&gt;&lt;/div&gt;
&lt;div class=&#34;ng&#34;&gt;&lt;span class=&#34;badge badge-compile&#34;&gt;compile&lt;/span&gt; key types are KEM-tagged&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;hpke-reject-row&#34;&gt;
&lt;div class=&#34;op&#34;&gt;Base-mode call with &lt;code&gt;Some(psk)&lt;/code&gt; argument&lt;/div&gt;
&lt;div class=&#34;rs&#34;&gt;&lt;span class=&#34;badge badge-runtime&#34;&gt;runtime&lt;/span&gt; &lt;code&gt;HpkeError::UnnecessaryPsk&lt;/code&gt;&lt;/div&gt;
&lt;div class=&#34;ng&#34;&gt;&lt;span class=&#34;badge badge-compile&#34;&gt;compile&lt;/span&gt; &lt;code&gt;seal_base&lt;/code&gt; has no PSK parameter&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Each row is a runtime error in hpke-rs and a compiler diagnostic in hpke-ng. The X-Wing/&lt;code&gt;seal_auth&lt;/code&gt; line is the cleanest example: X-Wing is a KEM, but it isn&amp;rsquo;t a Diffie-Hellman KEM, so it has no notion of authenticated encapsulation. In hpke-rs, calling &lt;code&gt;seal_auth&lt;/code&gt; on an X-Wing-configured &lt;code&gt;Hpke&lt;/code&gt; returns &lt;code&gt;Error::UnsupportedKemOperation&lt;/code&gt; at runtime. In hpke-ng, the trait bound on &lt;code&gt;seal_auth&lt;/code&gt; requires &lt;code&gt;K: AuthKem&lt;/code&gt;, and &lt;code&gt;XWingDraft06&lt;/code&gt; does not implement &lt;code&gt;AuthKem&lt;/code&gt; — so the call doesn&amp;rsquo;t compile. The same shape applies to the other rows.&lt;/p&gt;
&lt;p&gt;This isn&amp;rsquo;t theoretical. We&amp;rsquo;ve seen each of those four shapes in production code reviews — typically as a stale &lt;code&gt;match&lt;/code&gt; arm catching the runtime error and turning it into a generic 500. Surfacing them as compile errors deletes a class of code path entirely.&lt;/p&gt;
&lt;h2 id=&#34;feature-parity&#34;&gt;Feature parity&lt;/h2&gt;
&lt;p&gt;Before going further: the obvious skeptical question is &lt;em&gt;what got cut?&lt;/em&gt; The answer is one thing, deliberately, and it&amp;rsquo;s the thing that buys the wins everywhere else.&lt;/p&gt;
&lt;div class=&#34;hpke-parity reveal&#34;&gt;
&lt;div class=&#34;hpke-parity-head&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&#34;col-h&#34;&gt;hpke-rs&lt;/span&gt;&lt;span class=&#34;col-h&#34;&gt;hpke-ng&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-parity-row&#34;&gt;&lt;span class=&#34;cap&#34;&gt;DH KEMs (X25519, X448, P-256, P-384, P-521, secp256k1)&lt;/span&gt;&lt;span class=&#34;cell yes&#34;&gt;✓ all 6&lt;/span&gt;&lt;span class=&#34;cell yes&#34;&gt;✓ all 6&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-parity-row&#34;&gt;&lt;span class=&#34;cap&#34;&gt;Post-quantum KEMs (X-Wing draft-06, ML-KEM-768, ML-KEM-1024)&lt;/span&gt;&lt;span class=&#34;cell yes&#34;&gt;✓ all 3 (&lt;code&gt;experimental&lt;/code&gt;, upstream-flagged unstable)&lt;/span&gt;&lt;span class=&#34;cell yes&#34;&gt;✓ all 3 (&lt;code&gt;pq&lt;/code&gt; feature)&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-parity-row&#34;&gt;&lt;span class=&#34;cap&#34;&gt;KDFs (HKDF-SHA-256 / -384 / -512)&lt;/span&gt;&lt;span class=&#34;cell yes&#34;&gt;✓ all 3&lt;/span&gt;&lt;span class=&#34;cell yes&#34;&gt;✓ all 3&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-parity-row&#34;&gt;&lt;span class=&#34;cap&#34;&gt;AEADs (AES-128-GCM, AES-256-GCM, ChaCha20-Poly1305, ExportOnly)&lt;/span&gt;&lt;span class=&#34;cell yes&#34;&gt;✓ all 4&lt;/span&gt;&lt;span class=&#34;cell yes&#34;&gt;✓ all 4&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-parity-row&#34;&gt;&lt;span class=&#34;cap&#34;&gt;Modes (Base, Psk, Auth, AuthPsk)&lt;/span&gt;&lt;span class=&#34;cell yes&#34;&gt;✓ all 4&lt;/span&gt;&lt;span class=&#34;cell yes&#34;&gt;✓ all 4&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-parity-row&#34;&gt;&lt;span class=&#34;cap&#34;&gt;RFC 9180 KAT conformance (full vendored vector set)&lt;/span&gt;&lt;span class=&#34;cell yes&#34;&gt;✓ pass&lt;/span&gt;&lt;span class=&#34;cell yes&#34;&gt;✓ pass&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-parity-row&#34;&gt;&lt;span class=&#34;cap&#34;&gt;Typed &lt;code&gt;HpkeError&lt;/code&gt; variants&lt;/span&gt;&lt;span class=&#34;cell num&#34;&gt;11&lt;/span&gt;&lt;span class=&#34;cell num plus&#34;&gt;14&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-parity-row&#34;&gt;&lt;span class=&#34;cap&#34;&gt;Compile-time-rejected operation classes&lt;/span&gt;&lt;span class=&#34;cell num&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;cell num plus&#34;&gt;4&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-parity-row hpke-parity-flip&#34;&gt;&lt;span class=&#34;cap&#34;&gt;Pluggable crypto provider (RustCrypto / libcrux backends)&lt;/span&gt;&lt;span class=&#34;cell yes&#34;&gt;✓ both&lt;/span&gt;&lt;span class=&#34;cell no&#34;&gt;— RustCrypto only&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;hpke-ng supports every ciphersuite hpke-rs&amp;rsquo;s RustCrypto provider supports — six DH-based KEMs, three post-quantum KEMs, three KDFs, four AEADs (including ExportOnly), all four HPKE modes — and passes the full RFC 9180 KAT vector set against both libraries. There are 14 typed &lt;code&gt;HpkeError&lt;/code&gt; variants instead of 11, and four classes of operation that are compile errors instead of runtime errors. On every dimension that affects what your application can express, hpke-ng is a superset.&lt;/p&gt;
&lt;p&gt;The one thing hpke-rs has that hpke-ng deliberately doesn&amp;rsquo;t: a pluggable crypto provider. hpke-rs ships an &lt;code&gt;HpkeCrypto&lt;/code&gt; trait with two backend implementations, RustCrypto and libcrux; a deployment can swap one for the other at compile time. hpke-ng ships one provider, RustCrypto, and removes the abstraction. That&amp;rsquo;s the load-bearing tradeoff. It&amp;rsquo;s why &lt;code&gt;Hpke&amp;lt;...&amp;gt;&lt;/code&gt; is zero-sized, why &lt;code&gt;Context::seal&lt;/code&gt; is monomorphized rather than dispatched, why the workspace is one crate, and why the canonical call site is five arguments instead of seven.&lt;/p&gt;
&lt;p&gt;The libcrux half of that tradeoff is, in our view, not a feature worth chasing. Our &lt;a href=&#34;https://symbolic.software/blog/2026-02-05-cryspen/&#34;&gt;February audit&lt;/a&gt; of Cryspen&amp;rsquo;s &amp;ldquo;formally verified&amp;rdquo; libraries found undisclosed silent cryptographic failures in libcrux-ml-dsa (platform-dependent SHA-3 corruption that was patched without a public advisory), entropy-reducing pre-hash clamping in Ed25519, and a denial-of-service panic in libcrux-psq&amp;rsquo;s AES-GCM decryption path. Cryspen&amp;rsquo;s &lt;a href=&#34;https://symbolic.software/blog/2026-02-12-cryspen-response/&#34;&gt;public response&lt;/a&gt; acknowledged the findings without retracting the &amp;ldquo;highest level of assurance&amp;rdquo; marketing language attached to the library; our &lt;a href=&#34;https://symbolic.software/blog/2026-02-17-cryspen-mldsa/&#34;&gt;follow-up analysis&lt;/a&gt; and an &lt;a href=&#34;https://eprint.iacr.org/2026/192&#34;&gt;independent paper&lt;/a&gt; have since surfaced further specification deviations in libcrux-ml-dsa. The &amp;ldquo;formally verified&amp;rdquo; framing that draws users to libcrux does not, on the evidence we have assembled, describe the engineering you are getting. If you&amp;rsquo;re using RustCrypto anyway — which is what hpke-ng does by default and what most hpke-rs deployments do in practice — the provider abstraction is paying rent for a backend you would be wise to avoid.&lt;/p&gt;
&lt;h2 id=&#34;speed&#34;&gt;Speed&lt;/h2&gt;
&lt;p&gt;The full benchmark protocol is in &lt;code&gt;cargo bench --features comparative&lt;/code&gt; in the hpke-ng repository: criterion harness, sample size 40–60, 2–3 second measurement window, &lt;code&gt;RUSTFLAGS=&amp;quot;-C target-cpu=native&amp;quot;&lt;/code&gt;, &lt;code&gt;lto = &amp;quot;thin&amp;quot;&lt;/code&gt;, &lt;code&gt;codegen-units = 1&lt;/code&gt;. Apple Silicon M-series, macOS. The numbers below are the median across two independent bench runs. The post-quantum rows enable &lt;code&gt;hpke-rs-rust-crypto&lt;/code&gt;&amp;rsquo;s &lt;code&gt;experimental&lt;/code&gt; feature flag — without it, hpke-rs&amp;rsquo;s PQ KEMs return &lt;code&gt;UnsupportedKemOperation&lt;/code&gt; at runtime; the upstream comment on the gate is &amp;ldquo;broken and pre-releases. Disabling them until they are stable.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;The wins concentrate in four places: the &lt;strong&gt;post-quantum KEM path&lt;/strong&gt; (the largest deltas in the entire dataset, peaking at −55% on ML-KEM decap), the &lt;strong&gt;classical KEM encap/decap path&lt;/strong&gt;, the &lt;strong&gt;single-shot open path&lt;/strong&gt;, and the &lt;strong&gt;ChaCha20 setup paths&lt;/strong&gt;. These are the rows where hpke-rs&amp;rsquo;s per-call overhead — trait dispatch, allocator pressure, an enum match per primitive, a from-seed key reconstruction on every PQ decap — adds measurable framing cost on top of the underlying crypto. Start with the KEM operations on X25519:&lt;/p&gt;
&lt;div class=&#34;hpke-bench reveal&#34;&gt;
&lt;div class=&#34;hpke-bench-head&#34;&gt;
&lt;h4&gt;KEM operations · X25519&lt;/h4&gt;
&lt;div class=&#34;hpke-bench-key&#34;&gt;&lt;span class=&#34;key key-rs&#34;&gt;&lt;span class=&#34;d&#34;&gt;&lt;/span&gt;hpke-rs&lt;/span&gt;&lt;span class=&#34;key key-ng&#34;&gt;&lt;span class=&#34;d&#34;&gt;&lt;/span&gt;hpke-ng&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;hpke-bench-body&#34;&gt;
&lt;div class=&#34;hpke-bench-row&#34;&gt;&lt;span class=&#34;payload&#34;&gt;generate&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:86.0%&#34;&gt;&lt;span class=&#34;num&#34;&gt;7.69 µs&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;8.94 µs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-flip&#34;&gt;+16%&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-bench-row&#34;&gt;&lt;span class=&#34;payload&#34;&gt;derive_key&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:93.6%&#34;&gt;&lt;span class=&#34;num&#34;&gt;8.79 µs&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;9.39 µs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-flip&#34;&gt;+7%&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-bench-row&#34;&gt;&lt;span class=&#34;payload&#34;&gt;encap&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;38.00 µs&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:90.3%&#34;&gt;&lt;span class=&#34;num&#34;&gt;34.33 µs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-win&#34;&gt;−10%&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-bench-row&#34;&gt;&lt;span class=&#34;payload&#34;&gt;decap&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;36.73 µs&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:58.5%&#34;&gt;&lt;span class=&#34;num&#34;&gt;21.47 µs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-win&#34;&gt;−41%&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;hpke-bench-foot&#34;&gt;Lower is faster. Bars normalized per-row to &lt;code&gt;max(rs, ng)&lt;/code&gt;.&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The decap row tells the load-bearing story: hpke-ng caches the recipient&amp;rsquo;s serialized public-key bytes alongside the secret, so &lt;code&gt;decap&lt;/code&gt; no longer pays a base-point scalar multiplication just to rebuild the recipient-PK piece of &lt;code&gt;kem_context&lt;/code&gt; on every call. hpke-rs runs that scalar mult per decap. Encap is 10% faster from the same monomorphization trick — the &lt;code&gt;LabeledExtract&lt;/code&gt; + &lt;code&gt;LabeledExpand&lt;/code&gt; chain wrapping the raw Diffie-Hellman compiles to direct calls, where hpke-rs routes each through its enum-dispatched provider trait. The &lt;code&gt;generate&lt;/code&gt; and &lt;code&gt;derive_key_pair&lt;/code&gt; rows go the other way (+16% and +7%) because the public-key bytes are now computed and stored at construction time — a one-time cost paid for the per-call decap savings, on a one-call-per-keypair operation that doesn&amp;rsquo;t appear on any hot path. The same scalar-mult-saving trick lands much harder on the post-quantum decap rows we&amp;rsquo;ll see shortly.&lt;/p&gt;
&lt;p&gt;Setup is the combined fixed cost paid every time a sender or receiver context is constructed: KEM op + key schedule + Context allocation. The wins here are consistent over ChaCha20:&lt;/p&gt;
&lt;div class=&#34;hpke-bench reveal&#34;&gt;
&lt;div class=&#34;hpke-bench-head&#34;&gt;
&lt;h4&gt;Setup paths · sender / receiver / PSK&lt;/h4&gt;
&lt;div class=&#34;hpke-bench-key&#34;&gt;&lt;span class=&#34;key key-rs&#34;&gt;&lt;span class=&#34;d&#34;&gt;&lt;/span&gt;hpke-rs&lt;/span&gt;&lt;span class=&#34;key key-ng&#34;&gt;&lt;span class=&#34;d&#34;&gt;&lt;/span&gt;hpke-ng&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;hpke-bench-body&#34;&gt;
&lt;div class=&#34;hpke-bench-row&#34;&gt;&lt;span class=&#34;payload&#34;&gt;X25519+ChaCha20&lt;br&gt;sender (Base)&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;38.62 µs&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:88.5%&#34;&gt;&lt;span class=&#34;num&#34;&gt;34.17 µs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-win&#34;&gt;−12%&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-bench-row&#34;&gt;&lt;span class=&#34;payload&#34;&gt;X25519+ChaCha20&lt;br&gt;receiver (Base)&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;36.84 µs&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:69.1%&#34;&gt;&lt;span class=&#34;num&#34;&gt;25.45 µs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-win&#34;&gt;−31%&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-bench-row&#34;&gt;&lt;span class=&#34;payload&#34;&gt;X25519+ChaCha20&lt;br&gt;sender (PSK)&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;38.14 µs&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:88.8%&#34;&gt;&lt;span class=&#34;num&#34;&gt;33.86 µs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-win&#34;&gt;−11%&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-bench-row&#34;&gt;&lt;span class=&#34;payload&#34;&gt;X25519+AES-128&lt;br&gt;sender (Base)&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;40.29 µs&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:85.3%&#34;&gt;&lt;span class=&#34;num&#34;&gt;34.35 µs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-win&#34;&gt;−15%&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-bench-row&#34;&gt;&lt;span class=&#34;payload&#34;&gt;P-256+AES-128&lt;br&gt;sender (Base)&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;156.57 µs&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:93.0%&#34;&gt;&lt;span class=&#34;num&#34;&gt;145.59 µs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-win&#34;&gt;−7%&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-bench-row&#34;&gt;&lt;span class=&#34;payload&#34;&gt;K256+ChaCha20&lt;br&gt;sender (Base)&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;54.32 µs&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:87.8%&#34;&gt;&lt;span class=&#34;num&#34;&gt;47.67 µs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-win&#34;&gt;−12%&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;hpke-bench-foot&#34;&gt;Receiver-side wins are larger than sender-side because both `decap` and the post-decap key schedule benefit from the cached public-key bytes.&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The post-quantum KEMs add a structural dimension to the comparison that the classical KEMs don&amp;rsquo;t have. hpke-ng stores &lt;code&gt;MlKem*&lt;/code&gt; private keys as the 64-byte &lt;code&gt;(d, z)&lt;/code&gt; seed &lt;em&gt;plus&lt;/em&gt; the materialized FIPS 203 expanded decapsulation key — built once at construction, kept in the &lt;code&gt;PrivateKey&lt;/code&gt; wrapper, reused on every decap. hpke-rs stores only the seed and rebuilds the expanded key on every &lt;code&gt;setup_receiver&lt;/code&gt; call by re-running FIPS 203 KeyGen_internal. That single design choice is the load-bearing reason ML-KEM decap shows up at −54% to −55% — the largest single deltas in the dataset. ML-KEM-768 first:&lt;/p&gt;
&lt;div class=&#34;hpke-bench reveal&#34;&gt;
&lt;div class=&#34;hpke-bench-head&#34;&gt;
&lt;h4&gt;KEM operations · ML-KEM-768&lt;/h4&gt;
&lt;div class=&#34;hpke-bench-key&#34;&gt;&lt;span class=&#34;key key-rs&#34;&gt;&lt;span class=&#34;d&#34;&gt;&lt;/span&gt;hpke-rs&lt;/span&gt;&lt;span class=&#34;key key-ng&#34;&gt;&lt;span class=&#34;d&#34;&gt;&lt;/span&gt;hpke-ng&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;hpke-bench-body&#34;&gt;
&lt;div class=&#34;hpke-bench-row&#34;&gt;&lt;span class=&#34;payload&#34;&gt;generate&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:97.5%&#34;&gt;&lt;span class=&#34;num&#34;&gt;17.18 µs&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;17.62 µs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-tie&#34;&gt;+3%&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-bench-row&#34;&gt;&lt;span class=&#34;payload&#34;&gt;derive_key&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;14.49 µs&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:97.8%&#34;&gt;&lt;span class=&#34;num&#34;&gt;14.17 µs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-tie&#34;&gt;−2%&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-bench-row&#34;&gt;&lt;span class=&#34;payload&#34;&gt;encap&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;23.54 µs&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:63.0%&#34;&gt;&lt;span class=&#34;num&#34;&gt;14.84 µs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-win&#34;&gt;−37%&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-bench-row&#34;&gt;&lt;span class=&#34;payload&#34;&gt;decap&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;38.86 µs&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:45.1%&#34;&gt;&lt;span class=&#34;num&#34;&gt;17.53 µs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-win&#34;&gt;−55%&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;hpke-bench-foot&#34;&gt;decap (−55%) is the largest delta in the suite, encap (−37%) close behind — hpke-ng caches the expanded decapsulation key in the private key and the parsed encapsulation key in the public key; hpke-rs rebuilds both from raw bytes on every call.&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;ML-KEM-1024 — the higher-security parameter set — shows the same shape, larger absolute numbers, same architectural delta in the same place:&lt;/p&gt;
&lt;div class=&#34;hpke-bench reveal&#34;&gt;
&lt;div class=&#34;hpke-bench-head&#34;&gt;
&lt;h4&gt;KEM operations · ML-KEM-1024&lt;/h4&gt;
&lt;div class=&#34;hpke-bench-key&#34;&gt;&lt;span class=&#34;key key-rs&#34;&gt;&lt;span class=&#34;d&#34;&gt;&lt;/span&gt;hpke-rs&lt;/span&gt;&lt;span class=&#34;key key-ng&#34;&gt;&lt;span class=&#34;d&#34;&gt;&lt;/span&gt;hpke-ng&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;hpke-bench-body&#34;&gt;
&lt;div class=&#34;hpke-bench-row&#34;&gt;&lt;span class=&#34;payload&#34;&gt;generate&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:99.0%&#34;&gt;&lt;span class=&#34;num&#34;&gt;27.72 µs&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;28.00 µs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-tie&#34;&gt;+1%&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-bench-row&#34;&gt;&lt;span class=&#34;payload&#34;&gt;derive_key&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:96.4%&#34;&gt;&lt;span class=&#34;num&#34;&gt;23.19 µs&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;24.06 µs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-flip&#34;&gt;+4%&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-bench-row&#34;&gt;&lt;span class=&#34;payload&#34;&gt;encap&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;33.34 µs&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:70.4%&#34;&gt;&lt;span class=&#34;num&#34;&gt;23.46 µs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-win&#34;&gt;−30%&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-bench-row&#34;&gt;&lt;span class=&#34;payload&#34;&gt;decap&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;58.27 µs&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:46.7%&#34;&gt;&lt;span class=&#34;num&#34;&gt;27.24 µs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-win&#34;&gt;−53%&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;hpke-bench-foot&#34;&gt;Same pattern as ML-KEM-768 at higher security level: encap −30%, decap −53%. The +4% on `derive_key` is the cost of cloning the larger ML-KEM-1024 expanded encapsulation key into the public-key wrapper at construction.&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;X-Wing — the X25519 + ML-KEM-768 hybrid — picks up the same &lt;code&gt;DecapsulationKey&lt;/code&gt;-caching trick we applied to ML-KEM, plus the parsed &lt;code&gt;EncapsulationKey&lt;/code&gt; cache on the encap side. The decap delta lands close to the ML-KEM rows:&lt;/p&gt;
&lt;div class=&#34;hpke-bench reveal&#34;&gt;
&lt;div class=&#34;hpke-bench-head&#34;&gt;
&lt;h4&gt;KEM operations · X-Wing draft-06&lt;/h4&gt;
&lt;div class=&#34;hpke-bench-key&#34;&gt;&lt;span class=&#34;key key-rs&#34;&gt;&lt;span class=&#34;d&#34;&gt;&lt;/span&gt;hpke-rs&lt;/span&gt;&lt;span class=&#34;key key-ng&#34;&gt;&lt;span class=&#34;d&#34;&gt;&lt;/span&gt;hpke-ng&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;hpke-bench-body&#34;&gt;
&lt;div class=&#34;hpke-bench-row&#34;&gt;&lt;span class=&#34;payload&#34;&gt;generate&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;38.94 µs&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:98.9%&#34;&gt;&lt;span class=&#34;num&#34;&gt;38.52 µs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-tie&#34;&gt;−1%&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-bench-row&#34;&gt;&lt;span class=&#34;payload&#34;&gt;derive_key&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:95.2%&#34;&gt;&lt;span class=&#34;num&#34;&gt;35.81 µs&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;37.60 µs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-flip&#34;&gt;+5%&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-bench-row&#34;&gt;&lt;span class=&#34;payload&#34;&gt;encap&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;64.94 µs&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:86.3%&#34;&gt;&lt;span class=&#34;num&#34;&gt;56.06 µs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-win&#34;&gt;−14%&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-bench-row&#34;&gt;&lt;span class=&#34;payload&#34;&gt;decap&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;115.56 µs&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:62.5%&#34;&gt;&lt;span class=&#34;num&#34;&gt;72.25 µs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-win&#34;&gt;−38%&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;hpke-bench-foot&#34;&gt;decap −38%: the construction-side `expand_key` that `DecapsulationKey::from(seed)` runs (SHAKE-256 + ML-KEM-768 keygen) is now amortized to once per private key. The +5% on `derive_key` is the cost of cloning the parsed `EncapsulationKey` into the public-key wrapper at construction.&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The same pattern propagates to setup paths. &lt;code&gt;setup_sender&lt;/code&gt; is dominated by encap; &lt;code&gt;setup_receiver&lt;/code&gt; is dominated by decap; the ML-KEM rows widen accordingly:&lt;/p&gt;
&lt;div class=&#34;hpke-bench reveal&#34;&gt;
&lt;div class=&#34;hpke-bench-head&#34;&gt;
&lt;h4&gt;Setup paths · post-quantum (HKDF-SHA-256 + ChaCha20-Poly1305)&lt;/h4&gt;
&lt;div class=&#34;hpke-bench-key&#34;&gt;&lt;span class=&#34;key key-rs&#34;&gt;&lt;span class=&#34;d&#34;&gt;&lt;/span&gt;hpke-rs&lt;/span&gt;&lt;span class=&#34;key key-ng&#34;&gt;&lt;span class=&#34;d&#34;&gt;&lt;/span&gt;hpke-ng&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;hpke-bench-body&#34;&gt;
&lt;div class=&#34;hpke-bench-row&#34;&gt;&lt;span class=&#34;payload&#34;&gt;X-Wing&lt;br&gt;sender (Base)&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;65.30 µs&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:92.1%&#34;&gt;&lt;span class=&#34;num&#34;&gt;60.14 µs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-win&#34;&gt;−8%&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-bench-row&#34;&gt;&lt;span class=&#34;payload&#34;&gt;X-Wing&lt;br&gt;receiver (Base)&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;116.79 µs&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:66.3%&#34;&gt;&lt;span class=&#34;num&#34;&gt;77.45 µs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-win&#34;&gt;−34%&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-bench-row&#34;&gt;&lt;span class=&#34;payload&#34;&gt;ML-KEM-768&lt;br&gt;sender (Base)&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;23.77 µs&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:79.6%&#34;&gt;&lt;span class=&#34;num&#34;&gt;18.91 µs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-win&#34;&gt;−20%&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-bench-row&#34;&gt;&lt;span class=&#34;payload&#34;&gt;ML-KEM-768&lt;br&gt;receiver (Base)&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;39.80 µs&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:54.9%&#34;&gt;&lt;span class=&#34;num&#34;&gt;21.86 µs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-win&#34;&gt;−45%&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-bench-row&#34;&gt;&lt;span class=&#34;payload&#34;&gt;ML-KEM-1024&lt;br&gt;sender (Base)&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;33.82 µs&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:82.4%&#34;&gt;&lt;span class=&#34;num&#34;&gt;27.85 µs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-win&#34;&gt;−18%&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-bench-row&#34;&gt;&lt;span class=&#34;payload&#34;&gt;ML-KEM-1024&lt;br&gt;receiver (Base)&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;62.17 µs&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:50.4%&#34;&gt;&lt;span class=&#34;num&#34;&gt;31.33 µs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-win&#34;&gt;−50%&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;hpke-bench-foot&#34;&gt;18 post-quantum head-to-heads across KEM ops + setup: 12 wins, 4 ties, 2 losses (both on `derive_key_pair`, where caching the expanded keys at construction is paid back on every subsequent encap/decap).&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The single-shot open path — &lt;code&gt;setup_receiver&lt;/code&gt; + &lt;code&gt;Context::open&lt;/code&gt; for one message — is where hpke-ng wins most consistently across payload size. Six rows over four orders of magnitude, every row is hpke-ng:&lt;/p&gt;
&lt;div class=&#34;hpke-bench reveal&#34;&gt;
&lt;div class=&#34;hpke-bench-head&#34;&gt;
&lt;h4&gt;Single-shot open · X25519 + HKDF-SHA-256 + ChaCha20-Poly1305&lt;/h4&gt;
&lt;div class=&#34;hpke-bench-key&#34;&gt;&lt;span class=&#34;key key-rs&#34;&gt;&lt;span class=&#34;d&#34;&gt;&lt;/span&gt;hpke-rs&lt;/span&gt;&lt;span class=&#34;key key-ng&#34;&gt;&lt;span class=&#34;d&#34;&gt;&lt;/span&gt;hpke-ng&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;hpke-bench-body&#34;&gt;
&lt;div class=&#34;hpke-bench-row&#34;&gt;&lt;span class=&#34;payload&#34;&gt;64 B&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;40.30 µs&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:64.7%&#34;&gt;&lt;span class=&#34;num&#34;&gt;26.07 µs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-win&#34;&gt;−35%&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-bench-row&#34;&gt;&lt;span class=&#34;payload&#34;&gt;256 B&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;38.67 µs&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:68.1%&#34;&gt;&lt;span class=&#34;num&#34;&gt;26.32 µs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-win&#34;&gt;−32%&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-bench-row&#34;&gt;&lt;span class=&#34;payload&#34;&gt;1 KiB&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;39.23 µs&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:70.8%&#34;&gt;&lt;span class=&#34;num&#34;&gt;27.77 µs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-win&#34;&gt;−29%&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-bench-row&#34;&gt;&lt;span class=&#34;payload&#34;&gt;4 KiB&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;44.96 µs&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:70.9%&#34;&gt;&lt;span class=&#34;num&#34;&gt;31.88 µs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-win&#34;&gt;−29%&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-bench-row&#34;&gt;&lt;span class=&#34;payload&#34;&gt;16 KiB&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;65.58 µs&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:77.2%&#34;&gt;&lt;span class=&#34;num&#34;&gt;50.61 µs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-win&#34;&gt;−23%&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-bench-row&#34;&gt;&lt;span class=&#34;payload&#34;&gt;64 KiB&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;137.07 µs&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:93.1%&#34;&gt;&lt;span class=&#34;num&#34;&gt;127.55 µs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-win&#34;&gt;−7%&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;hpke-bench-foot&#34;&gt;Lower is faster. Open inherits the full setup-receiver win — including the cached `pk_bytes` shaving a scalar mult off every `decap` — so the small-payload regime where setup dominates picks up the largest deltas.&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The AES-128-GCM seal sweep shows hpke-ng 6–12% ahead from 64 B through 16 KiB — the cached AES cipher state (round keys plus the GHash precomputed table, built once at key-schedule time) eliminates the per-call expansion that hpke-rs runs on every seal — then converges to tied at 64 KiB as the AEAD primitive&amp;rsquo;s bulk-encryption cost dominates:&lt;/p&gt;
&lt;div class=&#34;hpke-bench reveal&#34;&gt;
&lt;div class=&#34;hpke-bench-head&#34;&gt;
&lt;h4&gt;Single-shot seal · X25519 + HKDF-SHA-256 + AES-128-GCM&lt;/h4&gt;
&lt;div class=&#34;hpke-bench-key&#34;&gt;&lt;span class=&#34;key key-rs&#34;&gt;&lt;span class=&#34;d&#34;&gt;&lt;/span&gt;hpke-rs&lt;/span&gt;&lt;span class=&#34;key key-ng&#34;&gt;&lt;span class=&#34;d&#34;&gt;&lt;/span&gt;hpke-ng&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;hpke-bench-body&#34;&gt;
&lt;div class=&#34;hpke-bench-row&#34;&gt;&lt;span class=&#34;payload&#34;&gt;64 B&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;39.73 µs&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:89.0%&#34;&gt;&lt;span class=&#34;num&#34;&gt;35.38 µs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-win&#34;&gt;−11%&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-bench-row&#34;&gt;&lt;span class=&#34;payload&#34;&gt;256 B&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;41.03 µs&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:88.5%&#34;&gt;&lt;span class=&#34;num&#34;&gt;36.33 µs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-win&#34;&gt;−12%&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-bench-row&#34;&gt;&lt;span class=&#34;payload&#34;&gt;1 KiB&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;42.94 µs&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:89.8%&#34;&gt;&lt;span class=&#34;num&#34;&gt;38.56 µs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-win&#34;&gt;−10%&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-bench-row&#34;&gt;&lt;span class=&#34;payload&#34;&gt;4 KiB&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;53.94 µs&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:92.0%&#34;&gt;&lt;span class=&#34;num&#34;&gt;49.64 µs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-win&#34;&gt;−8%&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-bench-row&#34;&gt;&lt;span class=&#34;payload&#34;&gt;16 KiB&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;99.41 µs&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:93.6%&#34;&gt;&lt;span class=&#34;num&#34;&gt;93.05 µs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-win&#34;&gt;−6%&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-bench-row&#34;&gt;&lt;span class=&#34;payload&#34;&gt;64 KiB&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:97.9%&#34;&gt;&lt;span class=&#34;num&#34;&gt;274.23 µs&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;280.15 µs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-tie&#34;&gt;+2%&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;hpke-bench-foot&#34;&gt;At 64 KiB the AES-NI bulk encryption cost dominates and the framing delta vanishes.&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The X25519+ChaCha20 seal sweep — the same shape but with a different AEAD — now lands 7–18% in hpke-ng&amp;rsquo;s favour from 16 B through 64 KiB, then converges to tied at 256 KiB where the AEAD primitive both libraries call identically dominates the wall time. The most diagnostic single benchmark in the suite is post-setup &lt;code&gt;Context::seal&lt;/code&gt; at the two ends of that spectrum:&lt;/p&gt;
&lt;div class=&#34;hpke-converge reveal-stagger&#34;&gt;
&lt;div class=&#34;hpke-converge-card&#34;&gt;
&lt;div class=&#34;title&#34;&gt;Context::seal · 64 B&lt;/div&gt;
&lt;div class=&#34;subtitle&#34;&gt;framing-dominant regime&lt;/div&gt;
&lt;div class=&#34;bars-vert&#34;&gt;
&lt;div class=&#34;vbar vbar-rs&#34; style=&#34;--h:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;260 ns&lt;/span&gt;&lt;span class=&#34;lib&#34;&gt;hpke-rs&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;vbar vbar-ng&#34; style=&#34;--h:85.0%&#34;&gt;&lt;span class=&#34;num&#34;&gt;221 ns&lt;/span&gt;&lt;span class=&#34;lib&#34;&gt;hpke-ng&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;result win&#34;&gt;−15% · framing only, same crypto&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;hpke-converge-card&#34;&gt;
&lt;div class=&#34;title&#34;&gt;Context::seal · 16 KiB&lt;/div&gt;
&lt;div class=&#34;subtitle&#34;&gt;primitive-dominant regime&lt;/div&gt;
&lt;div class=&#34;bars-vert&#34;&gt;
&lt;div class=&#34;vbar vbar-rs&#34; style=&#34;--h:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;24.60 µs&lt;/span&gt;&lt;span class=&#34;lib&#34;&gt;hpke-rs&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;vbar vbar-ng&#34; style=&#34;--h:99.4%&#34;&gt;&lt;span class=&#34;num&#34;&gt;24.46 µs&lt;/span&gt;&lt;span class=&#34;lib&#34;&gt;hpke-ng&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;result tied&#34;&gt;tied · at the primitive&#39;s ceiling&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This is the chart we kept coming back to during development. At 64 bytes, where framing dominates, hpke-ng is 15% faster: 221 ns versus 260 ns. The framing path inside hpke-ng&amp;rsquo;s &lt;code&gt;Context::seal&lt;/code&gt; is a fixed-size 12-byte stack array for the nonce, an XOR loop, and a direct AEAD call — no allocations, and the AEAD cipher state is built once at key-schedule time and reused. hpke-rs allocates a fresh &lt;code&gt;Vec&amp;lt;u8&amp;gt;&lt;/code&gt; per nonce computation and reconstructs cipher state from raw key bytes on every call.&lt;/p&gt;
&lt;p&gt;At 16 KiB, where the AEAD primitive dominates, both libraries converge to identical wall time. They share the same primitive crate, so this is the ceiling: the wall-clock cost of &lt;code&gt;ChaCha20Poly1305::encrypt_in_place_detached&lt;/code&gt; on this hardware, which neither library can dip below because both are calling the same code. hpke-ng matches it exactly — there&amp;rsquo;s no overhead left to remove, and the framing is as thin as the standard allows.&lt;/p&gt;
&lt;h2 id=&#34;memory&#34;&gt;Memory&lt;/h2&gt;
&lt;p&gt;Memory has two halves: the configuration and per-context state — where hpke-ng is uniformly smaller — and the per-key footprint, where the post-quantum KEMs introduce a deliberate tradeoff in the other direction.&lt;/p&gt;
&lt;div class=&#34;hpke-mem reveal-stagger&#34;&gt;
&lt;div class=&#34;hpke-mem-row&#34;&gt;
&lt;div class=&#34;hpke-mem-head&#34;&gt;&lt;span class=&#34;type&#34;&gt;&lt;code&gt;sizeof(Hpke&amp;lt;K, F, A&amp;gt;)&lt;/code&gt;&lt;/span&gt;&lt;span class=&#34;delta&#34;&gt;−344 B (zero-sized)&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;rectpair&#34;&gt;&lt;span class=&#34;lib&#34;&gt;hpke-rs&lt;/span&gt;&lt;div class=&#34;rect rect-rs&#34; style=&#34;--w:86%&#34;&gt;&lt;span class=&#34;bytes&#34;&gt;344 B&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class=&#34;rectpair&#34;&gt;&lt;span class=&#34;lib&#34;&gt;hpke-ng&lt;/span&gt;&lt;div class=&#34;rect rect-ng rect-zero&#34;&gt;&lt;span class=&#34;bytes&#34;&gt;PhantomData · 0 B&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;hpke-mem-row&#34;&gt;
&lt;div class=&#34;hpke-mem-head&#34;&gt;&lt;span class=&#34;type&#34;&gt;&lt;code&gt;sizeof(Context&amp;lt;_, _, ChaCha20Poly1305&amp;gt;)&lt;/code&gt;&lt;/span&gt;&lt;span class=&#34;delta&#34;&gt;−312 B (−78%)&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;rectpair&#34;&gt;&lt;span class=&#34;lib&#34;&gt;hpke-rs&lt;/span&gt;&lt;div class=&#34;rect rect-rs&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;bytes&#34;&gt;400 B&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class=&#34;rectpair&#34;&gt;&lt;span class=&#34;lib&#34;&gt;hpke-ng&lt;/span&gt;&lt;div class=&#34;rect rect-ng&#34; style=&#34;--w:22%&#34;&gt;&lt;span class=&#34;bytes&#34;&gt;88 B&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;hpke-mem-row&#34;&gt;
&lt;div class=&#34;hpke-mem-head&#34;&gt;&lt;span class=&#34;type&#34;&gt;&lt;code&gt;sizeof(Context&amp;lt;_, _, Aes128Gcm&amp;gt;)&lt;/code&gt;&lt;/span&gt;&lt;span class=&#34;delta&#34;&gt;+392 B (cached AES round keys + GHash)&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;rectpair&#34;&gt;&lt;span class=&#34;lib&#34;&gt;hpke-rs&lt;/span&gt;&lt;div class=&#34;rect rect-rs&#34; style=&#34;--w:50.5%&#34;&gt;&lt;span class=&#34;bytes&#34;&gt;400 B&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class=&#34;rectpair&#34;&gt;&lt;span class=&#34;lib&#34;&gt;hpke-ng&lt;/span&gt;&lt;div class=&#34;rect rect-ng&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;bytes&#34;&gt;792 B&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;hpke-mem-row&#34;&gt;
&lt;div class=&#34;hpke-mem-head&#34;&gt;&lt;span class=&#34;type&#34;&gt;&lt;code&gt;sizeof(Context&amp;lt;_, _, Aes256Gcm&amp;gt;)&lt;/code&gt;&lt;/span&gt;&lt;span class=&#34;delta&#34;&gt;+648 B (cached AES round keys + GHash)&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;rectpair&#34;&gt;&lt;span class=&#34;lib&#34;&gt;hpke-rs&lt;/span&gt;&lt;div class=&#34;rect rect-rs&#34; style=&#34;--w:38.2%&#34;&gt;&lt;span class=&#34;bytes&#34;&gt;400 B&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class=&#34;rectpair&#34;&gt;&lt;span class=&#34;lib&#34;&gt;hpke-ng&lt;/span&gt;&lt;div class=&#34;rect rect-ng&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;bytes&#34;&gt;1,048 B&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;code&gt;hpke-ng::Hpke&amp;lt;K, F, A&amp;gt;&lt;/code&gt; is &lt;code&gt;PhantomData&amp;lt;(K, F, A)&amp;gt;&lt;/code&gt;. There is no runtime presence; it costs zero bytes and &lt;code&gt;cargo expand&lt;/code&gt; confirms the compiler optimizes it out completely. &lt;code&gt;hpke-rs::Hpke&amp;lt;Crypto&amp;gt;&lt;/code&gt; carries a 256-byte ChaCha20 PRNG plus four enum discriminants and padding (344 bytes measured at the time of writing).&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Context&lt;/code&gt; is where hpke-ng&amp;rsquo;s per-AEAD specialization shows up. With &lt;code&gt;ChaCha20Poly1305&lt;/code&gt; the cipher state is just the 32-byte key, so &lt;code&gt;Context&lt;/code&gt; is 88 bytes — under a quarter of hpke-rs&amp;rsquo;s 400, since hpke-rs&amp;rsquo;s &lt;code&gt;Context&lt;/code&gt; carries a per-instance PRNG plus trait-object overhead that hpke-ng&amp;rsquo;s monomorphized design doesn&amp;rsquo;t need. With AES-GCM the trade goes the other way: hpke-ng caches the expanded round keys plus the precomputed GHash table inline so that &lt;code&gt;Context::seal&lt;/code&gt; doesn&amp;rsquo;t pay key-schedule expansion on every call. That&amp;rsquo;s the load-bearing reason AES-128 single-shot seal is 6–12% faster across small and mid payloads. Streaming AES applications get the throughput; ChaCha20 deployments stay at the small footprint.&lt;/p&gt;
&lt;p&gt;Practical impact: an application that holds a thousand long-lived ChaCha20 contexts — a server with persistent client sessions, a relay, MLS group state — saves ~310 KB of resident memory over hpke-rs. An AES-128-GCM deployment with the same shape pays ~390 KB of additional context state for the per-call seal-side speedup. Whether that&amp;rsquo;s a good trade is application-specific, and it&amp;rsquo;s now an explicit choice the type system makes visible.&lt;/p&gt;
&lt;h3 id=&#34;the-post-quantum-and-pk-cache-tradeoff-hpke-ng-private-keys-are-larger-across-the-board&#34;&gt;The post-quantum and PK-cache tradeoff: hpke-ng private keys are larger across the board&lt;/h3&gt;
&lt;p&gt;Per-key footprint is where the speed/memory trade lands explicitly. Every KEM private key in hpke-ng now caches material that hpke-rs reconstructs from raw bytes on demand: the recipient&amp;rsquo;s serialized public key for the DH-KEMs (so &lt;code&gt;decap&lt;/code&gt; doesn&amp;rsquo;t recompute it via base-point scalar multiplication), the expanded &lt;code&gt;x_wing::DecapsulationKey&lt;/code&gt; for X-Wing (so &lt;code&gt;decap&lt;/code&gt; doesn&amp;rsquo;t re-run SHAKE-256 + ML-KEM-768 keygen), and the materialized FIPS 203 decapsulation key for ML-KEM (same trick at the parameter-set boundary). The size impact is uniform and easy to reason about:&lt;/p&gt;
&lt;div class=&#34;hpke-bench reveal&#34;&gt;
&lt;div class=&#34;hpke-bench-head&#34;&gt;
&lt;h4&gt;KEM private key footprint · stack + heap, in bytes&lt;/h4&gt;
&lt;div class=&#34;hpke-bench-key&#34;&gt;&lt;span class=&#34;key key-rs&#34;&gt;&lt;span class=&#34;d&#34;&gt;&lt;/span&gt;hpke-rs&lt;/span&gt;&lt;span class=&#34;key key-ng&#34;&gt;&lt;span class=&#34;d&#34;&gt;&lt;/span&gt;hpke-ng&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;hpke-bench-body&#34;&gt;
&lt;div class=&#34;hpke-bench-row&#34;&gt;&lt;span class=&#34;payload&#34;&gt;X25519&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:63.6%&#34;&gt;&lt;span class=&#34;num&#34;&gt;56 B&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;88 B&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-flip&#34;&gt;+32 B&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-bench-row&#34;&gt;&lt;span class=&#34;payload&#34;&gt;P-256&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:46.3%&#34;&gt;&lt;span class=&#34;num&#34;&gt;56 B&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;121 B&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-flip&#34;&gt;+65 B&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-bench-row&#34;&gt;&lt;span class=&#34;payload&#34;&gt;X-Wing&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:3.3%&#34;&gt;&lt;span class=&#34;num&#34;&gt;56 B&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;1,698 B&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-flip&#34;&gt;+1,642 B&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-bench-row&#34;&gt;&lt;span class=&#34;payload&#34;&gt;ML-KEM-768&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:2.7%&#34;&gt;&lt;span class=&#34;num&#34;&gt;88 B&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;3,266 B&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-flip&#34;&gt;+3,178 B&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-bench-row&#34;&gt;&lt;span class=&#34;payload&#34;&gt;ML-KEM-1024&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:2.1%&#34;&gt;&lt;span class=&#34;num&#34;&gt;88 B&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;4,290 B&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-flip&#34;&gt;+4,202 B&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;hpke-bench-foot&#34;&gt;Every row spends extra memory at private-key construction in exchange for not paying the same work on every subsequent `decap`. That is the explicit memory cost of the −38% to −55% decap deltas.&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;So the trade is concrete: an extra 32–65 B per DH private key, ~1.7 KB extra per X-Wing private key, ~3.2 KB extra per ML-KEM-768 private key, and ~4.2 KB extra per ML-KEM-1024 private key. In exchange the recipient skips a base-point scalar mult per DH &lt;code&gt;decap&lt;/code&gt;, a SHAKE-256 + ML-KEM-768 keygen per X-Wing &lt;code&gt;decap&lt;/code&gt;, and a FIPS 203 KeyGen_internal per ML-KEM &lt;code&gt;decap&lt;/code&gt;. For a server pinning a few thousand long-lived receiver keys this is good arithmetic in essentially every setting we can think of, a possibly-bad trade on a microcontroller pinning very few keys, and we&amp;rsquo;d rather you have the numbers than guess.&lt;/p&gt;
&lt;p&gt;Public keys grow in the same direction on the post-quantum side — hpke-ng now caches the parsed &lt;code&gt;EncapsulationKey&lt;/code&gt; alongside the wire bytes so &lt;code&gt;encap&lt;/code&gt; doesn&amp;rsquo;t re-decode the 1,184/1,568-byte payload on every call. X-Wing public keys are 1,656 B, ML-KEM-768 public keys are 1,624 B, ML-KEM-1024 public keys are 2,136 B (vs hpke-rs&amp;rsquo;s &lt;code&gt;Vec&amp;lt;u8&amp;gt;&lt;/code&gt; of just the wire bytes). DH public keys are unchanged: 32 B for X25519, 96 B for P-256 (uncompressed encoded point form).&lt;/p&gt;
&lt;h2 id=&#34;smaller&#34;&gt;Smaller&lt;/h2&gt;
&lt;div class=&#34;hpke-shrink reveal&#34;&gt;
&lt;div class=&#34;hpke-shrink-head&#34;&gt;&lt;h4&gt;Project surface area&lt;/h4&gt;&lt;div class=&#34;hpke-bench-key&#34;&gt;&lt;span class=&#34;key key-rs&#34;&gt;&lt;span class=&#34;d&#34;&gt;&lt;/span&gt;hpke-rs&lt;/span&gt;&lt;span class=&#34;key key-ng&#34;&gt;&lt;span class=&#34;d&#34;&gt;&lt;/span&gt;hpke-ng&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-shrink-body&#34;&gt;
&lt;div class=&#34;hpke-shrink-row&#34;&gt;&lt;span class=&#34;metric&#34;&gt;End-user binary (stripped, release)&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;561 KB&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:69.8%&#34;&gt;&lt;span class=&#34;num&#34;&gt;392 KB&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-win&#34;&gt;−30%&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-shrink-row&#34;&gt;&lt;span class=&#34;metric&#34;&gt;Total project code (cloc)&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;5,631&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:85.5%&#34;&gt;&lt;span class=&#34;num&#34;&gt;4,817&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-win&#34;&gt;−14%&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-shrink-row&#34;&gt;&lt;span class=&#34;metric&#34;&gt;Library source (cloc)&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;2,623&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:92.5%&#34;&gt;&lt;span class=&#34;num&#34;&gt;2,426&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-win&#34;&gt;−8%&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-shrink-row&#34;&gt;&lt;span class=&#34;metric&#34;&gt;Test code (cloc)&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;2,230&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:50.4%&#34;&gt;&lt;span class=&#34;num&#34;&gt;1,124&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-win&#34;&gt;−50%&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-shrink-row&#34;&gt;&lt;span class=&#34;metric&#34;&gt;Bench code (cloc)&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:64.4%&#34;&gt;&lt;span class=&#34;num&#34;&gt;759&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;1,179&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-flip&#34;&gt;+55%&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-shrink-row&#34;&gt;&lt;span class=&#34;metric&#34;&gt;Crates in workspace&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;4&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:25%&#34;&gt;&lt;span class=&#34;num&#34;&gt;1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-win&#34;&gt;−75%&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-shrink-row&#34;&gt;&lt;span class=&#34;metric&#34;&gt;User-facing Cargo features&lt;/span&gt;&lt;div class=&#34;bars&#34;&gt;&lt;div class=&#34;bar bar-rs&#34; style=&#34;--w:100%&#34;&gt;&lt;span class=&#34;num&#34;&gt;11&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;bar bar-ng&#34; style=&#34;--w:63.6%&#34;&gt;&lt;span class=&#34;num&#34;&gt;7&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;span class=&#34;delta delta-win&#34;&gt;−36%&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Expanding on the above:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;End-user binary, −30%.&lt;/strong&gt; A minimal application — generate a key, seal a message, open it back, ten lines of Rust — compiled with &lt;code&gt;RUSTFLAGS=&amp;quot;-C target-cpu=native&amp;quot;&lt;/code&gt;, &lt;code&gt;lto=&amp;quot;thin&amp;quot;&lt;/code&gt;, &lt;code&gt;codegen-units=1&lt;/code&gt;, &lt;code&gt;strip=&amp;quot;symbols&amp;quot;&lt;/code&gt;. hpke-ng comes in at 392 KB; hpke-rs at 561 KB. 168 KB is not nothing on embedded targets, in WASM bundles, or in CDN-served binaries.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Library code, −8%.&lt;/strong&gt; hpke-ng is 197 lines smaller than hpke-rs at the library level (2,426 vs 2,623), while implementing strictly more — the full HPKE surface plus the optional post-quantum suite (X-Wing draft-06, ML-KEM-768, ML-KEM-1024) that hpke-rs reaches only via experimental feature flags. The type-state design earns its keep here: ciphersuite selection lives entirely in the type system, so there is no provider trait, no per-primitive enum dispatch, and no glue between the two. Inline &lt;code&gt;#[cfg(test)]&lt;/code&gt; modules are kept tight — anything covered by &lt;code&gt;tests/roundtrip.rs&lt;/code&gt;&amp;rsquo;s 59-cell macro matrix is deleted from &lt;code&gt;src/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Test code, −50%.&lt;/strong&gt; hpke-rs&amp;rsquo;s test suite is 167 tests in 2,230 lines; hpke-ng&amp;rsquo;s is 128 tests in 1,124 lines, with deeper coverage on roundtrips (59 macro-generated &lt;code&gt;(mode, KEM, KDF, AEAD)&lt;/code&gt; combinations vs hpke-rs&amp;rsquo;s 17 hand-written cases). The reduction is structural — the type system carries information that would otherwise be repeated test setup, and the &lt;code&gt;roundtrip!&lt;/code&gt; macro generates one test per supported configuration from a single declaration.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bench code, +55%.&lt;/strong&gt; This is the one row that runs against the section&amp;rsquo;s grain, and it&amp;rsquo;s deliberate. hpke-ng&amp;rsquo;s bench harness has grown to 1,179 lines while hpke-rs&amp;rsquo;s stays at 759 — the extra 420 lines are coverage, not waste. A single &lt;code&gt;cargo bench --features comparative&lt;/code&gt; reproduces every head-to-head number in this post, against a real hpke-rs install pulled in as a dev-dependency, including the full post-quantum suite (X-Wing, ML-KEM-768, ML-KEM-1024) that hpke-rs ships only behind an experimental feature flag. hpke-rs distributes its bench code across twelve per-provider files; hpke-ng keeps the comparative numbers in one place, which is what made the 62-row table at the top of this post possible at all.&lt;/p&gt;
&lt;p&gt;The one place this chart doesn&amp;rsquo;t reach is the fuzz harnesses. hpke-ng spends substantially &lt;em&gt;more&lt;/em&gt; code on cargo-fuzz targets than hpke-rs does — deliberately so. That&amp;rsquo;s the next section.&lt;/p&gt;
&lt;h2 id=&#34;harder&#34;&gt;Harder&lt;/h2&gt;
&lt;div class=&#34;hpke-harden reveal&#34;&gt;
&lt;div class=&#34;hpke-panel-bar&#34;&gt;
&lt;div class=&#34;left&#34;&gt;&lt;div class=&#34;traffic&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;span class=&#34;filename&#34;&gt;cargo test · &lt;span class=&#34;stem&#34;&gt;--features pq,kat-internals,differential&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;span class=&#34;status&#34;&gt;&lt;span class=&#34;pulse&#34;&gt;&lt;/span&gt;128 / 128 passing&lt;/span&gt;
&lt;/div&gt;
&lt;div class=&#34;hpke-harden-grid&#34;&gt;
&lt;div class=&#34;harden-stats&#34;&gt;
&lt;div class=&#34;stat&#34;&gt;&lt;div class=&#34;big&#34;&gt;&lt;span data-counter-target=&#34;128&#34; data-counter-from=&#34;0&#34;&gt;128&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;label&#34;&gt;tests passing&lt;/div&gt;&lt;/div&gt;
&lt;div class=&#34;stat&#34;&gt;&lt;div class=&#34;big&#34;&gt;1.9&lt;span class=&#34;unit&#34;&gt;s&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;label&#34;&gt;total wall time&lt;/div&gt;&lt;/div&gt;
&lt;div class=&#34;stat&#34;&gt;&lt;div class=&#34;big&#34;&gt;&lt;span data-counter-target=&#34;37&#34; data-counter-from=&#34;0&#34;&gt;37&lt;/span&gt;&lt;span class=&#34;unit&#34;&gt;×&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;label&#34;&gt;faster than hpke-rs&#39;s KAT runner&lt;/div&gt;&lt;/div&gt;
&lt;div class=&#34;stat&#34;&gt;&lt;div class=&#34;big&#34;&gt;4 &lt;span class=&#34;of&#34;&gt;/ 1&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;label&#34;&gt;cargo-fuzz targets &lt;span class=&#34;dim&#34;&gt;(hpke-ng / hpke-rs)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;harden-dots&#34; data-total=&#34;128&#34;&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;harden-categories&#34;&gt;
&lt;div class=&#34;cat-line&#34;&gt;&lt;span class=&#34;name&#34;&gt;unit (lib)&lt;/span&gt;&lt;span class=&#34;v&#34;&gt;46/46&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;cat-line&#34;&gt;&lt;span class=&#34;name&#34;&gt;RFC 9180 KAT&lt;/span&gt;&lt;span class=&#34;v&#34;&gt;13/13&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;cat-line&#34;&gt;&lt;span class=&#34;name&#34;&gt;roundtrip matrix&lt;/span&gt;&lt;span class=&#34;v&#34;&gt;59/59&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;cat-line&#34;&gt;&lt;span class=&#34;name&#34;&gt;differential vs hpke-rs&lt;/span&gt;&lt;span class=&#34;v&#34;&gt;8/8&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;cat-line&#34;&gt;&lt;span class=&#34;name&#34;&gt;doctests&lt;/span&gt;&lt;span class=&#34;v&#34;&gt;2/2&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;cat-line&#34;&gt;&lt;span class=&#34;name&#34;&gt;cargo-fuzz targets&lt;/span&gt;&lt;span class=&#34;v&#34;&gt;4 / 4 clean&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The 1.9-second figure is the headline number. The full hpke-ng test matrix — 128 tests across library unit tests, RFC 9180 KAT, generative roundtrips across every ciphersuite × mode combination, and byte-by-byte differential vs hpke-rs — runs in about 1.9 seconds. The roundtrip layer alone is 59 macro-generated tests covering every supported (mode, KEM, KDF, AEAD) combination including all four post-quantum and X-Wing/ML-KEM rows. hpke-rs&amp;rsquo;s KAT runner is structured as a single test that iterates 144 vectors sequentially and takes about 70 seconds. Same vectors, same coverage, structured to take advantage of &lt;code&gt;cargo test&lt;/code&gt;&amp;rsquo;s thread pool.&lt;/p&gt;
&lt;p&gt;For day-to-day development the headline number understates the impact. A 70-second feedback loop is one you avoid running until you&amp;rsquo;re &amp;ldquo;done&amp;rdquo;; a 1.9-second feedback loop is one you run after every save.&lt;/p&gt;
&lt;p&gt;The fuzz layer is where hpke-ng makes its biggest investment in lines of code. hpke-rs ships one cargo-fuzz target — a seal/open harness for one ciphersuite. hpke-ng ships four:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;pk_from_bytes&lt;/code&gt;&lt;/strong&gt; — fuzzes public-key parsing for &lt;strong&gt;all 9 KEMs&lt;/strong&gt; (X25519, X448, P-256, P-384, P-521, secp256k1, X-Wing, ML-KEM-768, ML-KEM-1024).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;enc_from_bytes&lt;/code&gt;&lt;/strong&gt; — fuzzes encapsulated-key parsing for all 9 KEMs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;key_schedule&lt;/code&gt;&lt;/strong&gt; — fuzzes the internal key schedule with arbitrary mode bytes (including invalid &lt;code&gt;0x04..=0xFF&lt;/code&gt; mode values), arbitrary PSK / PSK-ID combinations, and arbitrary shared secrets.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;open&lt;/code&gt;&lt;/strong&gt; — fuzzes &lt;code&gt;Hpke::open_base&lt;/code&gt; with arbitrary &lt;code&gt;[encap || ciphertext]&lt;/code&gt; byte splits against a fixed receiver keypair.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The shared invariant across all four is that &lt;em&gt;panics are bugs&lt;/em&gt;. Authentication failures, decode errors, length mismatches — all expected outcomes that the harness considers a successful run. A panic, a misaligned-pointer fault, or a debug-assertion failure under cargo-fuzz&amp;rsquo;s instrumentation is a finding. As of release, all four targets run clean.&lt;/p&gt;
&lt;p&gt;There are also several structural footgun-prevention details that don&amp;rsquo;t show up in the fuzz output but are worth listing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;Context&lt;/code&gt; is not &lt;code&gt;Clone&lt;/code&gt;.&lt;/strong&gt; Cloning an HPKE context lets two callers reuse the same &lt;code&gt;(key, base_nonce, seq)&lt;/code&gt; triple and produce a nonce-reuse bug — the kind of bug that&amp;rsquo;s invisible until it isn&amp;rsquo;t and unrecoverable when it surfaces. hpke-ng&amp;rsquo;s &lt;code&gt;Context&lt;/code&gt; deliberately doesn&amp;rsquo;t implement &lt;code&gt;Clone&lt;/code&gt;; cloning is a compile error.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;Context::seal&lt;/code&gt; refuses to encrypt at &lt;code&gt;seq == u64::MAX&lt;/code&gt;.&lt;/strong&gt; A pre-check, before nonce computation. This makes nonce-reuse via counter wraparound structurally impossible regardless of how the caller handles a &lt;code&gt;MessageLimitReached&lt;/code&gt; error.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;All-zero shared-secret rejection (RFC 9180 §7.1.4)&lt;/strong&gt; uses &lt;code&gt;subtle::ConstantTimeEq&lt;/code&gt; for X25519 and X448. An attacker who supplies a small-order point and watches for timing variance is one of the more subtle attacks on DHKEM; the constant-time comparison closes it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AEAD nonce length is enforced at compile time.&lt;/strong&gt; &lt;code&gt;Context::compute_nonce&lt;/code&gt; uses a const assertion that the AEAD&amp;rsquo;s &lt;code&gt;NONCE_LEN&lt;/code&gt; is between 8 and 12 bytes. Any AEAD that violates this is a compile error at the call site that uses it.&lt;/p&gt;
&lt;h2 id=&#34;interop&#34;&gt;Interop&lt;/h2&gt;
&lt;p&gt;We tested interop two ways. The first is RFC 9180 known-answer tests: both libraries are run against the same vendored test vector JSON (8 MB, derived from RFC 9180&amp;rsquo;s own test vector tooling) and required to produce byte-equal &lt;code&gt;key&lt;/code&gt;, &lt;code&gt;base_nonce&lt;/code&gt;, &lt;code&gt;exporter_secret&lt;/code&gt;, decrypted ciphertexts, and exported values for every vector. The second is byte-by-byte differential testing: a deterministic &lt;code&gt;ChaCha20Rng&lt;/code&gt; feeds identical inputs to both libraries; hpke-ng plays sender, hpke-rs plays receiver, and every byte that crosses the wire is asserted equal. Roughly 600 byte-equality assertions per CI run, all passing.&lt;/p&gt;
&lt;div class=&#34;hpke-interop reveal&#34;&gt;
&lt;div class=&#34;hpke-interop-head&#34;&gt;&lt;span class=&#34;suite-h&#34;&gt;Ciphersuite&lt;/span&gt;&lt;span class=&#34;cell-h&#34;&gt;RFC 9180 KAT&lt;/span&gt;&lt;span class=&#34;cell-h&#34;&gt;Differential vs hpke-rs&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-interop-row&#34;&gt;&lt;span class=&#34;suite&#34;&gt;DHKEM(X25519, SHA-256) × ChaCha20-Poly1305&lt;/span&gt;&lt;span class=&#34;cell pass&#34;&gt;✓ Base / Psk / Auth / AuthPsk&lt;/span&gt;&lt;span class=&#34;cell pass&#34;&gt;✓ Base + Psk&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-interop-row&#34;&gt;&lt;span class=&#34;suite&#34;&gt;DHKEM(X25519, SHA-256) × AES-128-GCM&lt;/span&gt;&lt;span class=&#34;cell pass&#34;&gt;✓ Base / Psk&lt;/span&gt;&lt;span class=&#34;cell pass&#34;&gt;✓ Base&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-interop-row&#34;&gt;&lt;span class=&#34;suite&#34;&gt;DHKEM(X25519, SHA-256) × AES-256-GCM&lt;/span&gt;&lt;span class=&#34;cell pass&#34;&gt;✓ Base / Psk&lt;/span&gt;&lt;span class=&#34;cell pass&#34;&gt;✓ Base&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-interop-row&#34;&gt;&lt;span class=&#34;suite&#34;&gt;DHKEM(X25519, SHA-256) × ExportOnly&lt;/span&gt;&lt;span class=&#34;cell pass&#34;&gt;✓ Base / Psk&lt;/span&gt;&lt;span class=&#34;cell na&#34;&gt;— no AEAD&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-interop-row&#34;&gt;&lt;span class=&#34;suite&#34;&gt;DHKEM(P-256, SHA-256) × ChaCha20-Poly1305&lt;/span&gt;&lt;span class=&#34;cell pass&#34;&gt;✓ Base / Psk&lt;/span&gt;&lt;span class=&#34;cell pass&#34;&gt;✓ via KAT&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-interop-row&#34;&gt;&lt;span class=&#34;suite&#34;&gt;DHKEM(P-256, SHA-256) × AES-128-GCM&lt;/span&gt;&lt;span class=&#34;cell pass&#34;&gt;✓ Base / Psk / Auth / AuthPsk&lt;/span&gt;&lt;span class=&#34;cell pass&#34;&gt;✓ Base + Psk&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-interop-row&#34;&gt;&lt;span class=&#34;suite&#34;&gt;DHKEM(P-521, SHA-512) × AES-256-GCM&lt;/span&gt;&lt;span class=&#34;cell pass&#34;&gt;✓ Base / Psk / Auth / AuthPsk&lt;/span&gt;&lt;span class=&#34;cell na&#34;&gt;— hpke-rs/RustCrypto unsupported&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-interop-row&#34;&gt;&lt;span class=&#34;suite&#34;&gt;DHKEM(secp256k1, SHA-256) × ChaCha20-Poly1305&lt;/span&gt;&lt;span class=&#34;cell pass&#34;&gt;✓ Base / Psk&lt;/span&gt;&lt;span class=&#34;cell pass&#34;&gt;✓ via KAT&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-interop-row&#34;&gt;&lt;span class=&#34;suite&#34;&gt;DHKEM(X448, SHA-512) × ChaCha20-Poly1305&lt;/span&gt;&lt;span class=&#34;cell pass&#34;&gt;✓ Base / Psk&lt;/span&gt;&lt;span class=&#34;cell na&#34;&gt;— hpke-rs/RustCrypto unsupported&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-interop-row&#34;&gt;&lt;span class=&#34;suite&#34;&gt;ML-KEM-768 × ChaCha20-Poly1305&lt;/span&gt;&lt;span class=&#34;cell na&#34;&gt;— no RFC vectors&lt;/span&gt;&lt;span class=&#34;cell na&#34;&gt;— seed-derivation differs by design&lt;/span&gt;&lt;/div&gt;
&lt;div class=&#34;hpke-interop-row&#34;&gt;&lt;span class=&#34;suite&#34;&gt;X-Wing draft-06 × ChaCha20-Poly1305&lt;/span&gt;&lt;span class=&#34;cell na&#34;&gt;— no RFC vectors&lt;/span&gt;&lt;span class=&#34;cell na&#34;&gt;— seed-derivation differs by design&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Some gaps worth covering, for full disclosure:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Auth and AuthPsk differential.&lt;/strong&gt; hpke-rs&amp;rsquo;s &lt;code&gt;seed()&lt;/code&gt; injects raw bytes for the base ephemeral; for Auth modes there is also a sender static keypair derived earlier, before any seed-injection happens. Aligning the two libraries&amp;rsquo; state for byte-by-byte Auth-mode differential testing would require a deeper hpke-rs API hook than &lt;code&gt;hpke-test-prng&lt;/code&gt; exposes. Auth/AuthPsk-mode interop is verified at the KAT layer instead — both libraries pass the X25519+ChaCha20 Auth/AuthPsk vectors, the P-256+AES-128 vectors, and the P-521+AES-256 vectors.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Post-quantum differential.&lt;/strong&gt; hpke-rs&amp;rsquo;s X-Wing and ML-KEM implementations use different SHAKE-256 seeding from hpke-ng&amp;rsquo;s RFC 9180 §7.1.3-compliant &lt;code&gt;derive_key_pair&lt;/code&gt; construction, so the libraries produce different ephemeral keys from the same IKM bytes. The encap wire format itself is determined by the underlying KEM crate (which both use), so they should agree at the wire level — but we haven&amp;rsquo;t tested it in this repository, and we&amp;rsquo;d rather call that out than pretend otherwise.&lt;/p&gt;
&lt;h2 id=&#34;migrate-today&#34;&gt;Migrate today&lt;/h2&gt;
&lt;p&gt;Both libraries pass the same KATs against the same primitive crates. If your code uses HPKE through a small wrapper — which most production HPKE code does — switching is mechanical:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-rust&#34; data-lang=&#34;rust&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// hpke-rs
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;mut&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;hpke&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Hpke&lt;/span&gt;::&lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;HpkeRustCrypto&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;gt;&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Mode&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;Base&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;KemAlgorithm&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;DhKem25519&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;KdfAlgorithm&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;HkdfSha256&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;AeadAlgorithm&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;ChaCha20Poly1305&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;kp&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;hpke&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;generate_key_pair&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;?&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sk&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;pk&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;kp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;into_keys&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;enc&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ct&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;hpke&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;seal&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;pk&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;info&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;aad&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;pt&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;None&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;None&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;None&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;?&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// hpke-ng
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;type&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;Suite&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Hpke&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;DhKemX25519HkdfSha256&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;HkdfSha256&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ChaCha20Poly1305&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;mut&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;os&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;OsRng&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;mut&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rng&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;os&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;unwrap_mut&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sk&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;pk&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;DhKemX25519HkdfSha256&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;generate&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;mut&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rng&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;?&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;enc&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ct&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Suite&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;seal_base&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;mut&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rng&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;pk&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;info&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;aad&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;pt&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;?&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The most common migration shape: define a &lt;code&gt;type Suite = Hpke&amp;lt;…, …, …&amp;gt;&lt;/code&gt; alias once, change &lt;code&gt;hpke.seal&lt;/code&gt; calls to &lt;code&gt;Suite::seal_base&lt;/code&gt; (or &lt;code&gt;seal_psk&lt;/code&gt; / &lt;code&gt;seal_auth&lt;/code&gt; / &lt;code&gt;seal_auth_psk&lt;/code&gt; per mode), thread an &lt;code&gt;&amp;amp;mut rng&lt;/code&gt; through the call sites that need encap entropy, drop the &lt;code&gt;Option&lt;/code&gt; placeholders.&lt;/p&gt;
&lt;h2 id=&#34;get-it&#34;&gt;Get it&lt;/h2&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-toml&#34; data-lang=&#34;toml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;dependencies&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;hpke-ng&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;0.1.0-rc.3&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href=&#34;https://github.com/symbolicsoft/hpke-ng&#34;&gt;github.com/symbolicsoft/hpke-ng&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;docs.rs:&lt;/strong&gt; &lt;a href=&#34;https://docs.rs/hpke-ng&#34;&gt;docs.rs/hpke-ng&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;License:&lt;/strong&gt; Apache-2.0 OR MIT&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MSRV:&lt;/strong&gt; Rust 1.95 (edition 2024)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you find a row in our benchmark suite that&amp;rsquo;s wrong, an interop gap we haven&amp;rsquo;t documented, or a footgun we missed, file an issue. We&amp;rsquo;d rather know.&lt;/p&gt;
&lt;script&gt;
(function () {
  var grids = document.querySelectorAll(&#39;.harden-dots&#39;);
  if (grids.length === 0) return;
  grids.forEach(function (grid) {
    var total = Number(grid.dataset.total) || 91;
    for (var i = 0; i &lt; total; i++) {
      var d = document.createElement(&#39;span&#39;);
      d.className = &#39;hpke-harden-dot&#39;;
      d.style.animationDelay = (i * 14 / 1000).toFixed(3) + &#39;s&#39;;
      grid.appendChild(d);
    }
  });
})();
&lt;/script&gt;
</content:encoded>
      <category>Software</category>
      <category>Research</category>
      <category>HPKE</category>
      <category>Rust</category>
      <category>Post-Quantum</category>
      
    </item>
    
    <item>
      <title>Announcing the Post-Quantum Migration Playbook</title>
      <link>https://symbolic.software/blog/2026-05-06-pq-migration-playbook/</link>
      <pubDate>Wed, 06 May 2026 00:00:00 &#43;0000</pubDate>
      <guid>https://symbolic.software/blog/2026-05-06-pq-migration-playbook/</guid>
      <description>A 52-page practitioner guide for engineers and architects working on post-quantum migration, alongside an interactive scorecard and TLS scanner at pq-migration.symbolic.software.</description>
      <content:encoded>&lt;p&gt;We get the same questions in almost every engagement now. &lt;em&gt;Which post-quantum primitive should we pick? When do we hybridize, and when do we stop? How do we migrate TLS without breaking production? Which library can we trust? What should we test for?&lt;/em&gt; The answers have stabilized enough that writing them down once feels more useful than reciting them in another kickoff call.&lt;/p&gt;
&lt;p&gt;So we did. The &lt;strong&gt;&lt;a href=&#34;https://pq-migration.symbolic.software/static/pdf/playbook.pdf&#34;&gt;Post-Quantum Migration Playbook&lt;/a&gt;&lt;/strong&gt; is a 52-page guide for the engineer or architect who has been told their system needs to be &amp;ldquo;post-quantum ready&amp;rdquo; and is now trying to figure out what that actually means in practice. It is organized around decisions, not theory — each chapter is short, scoped to a single migration concern, and ends with our recommendation and the contingency it depends on. Skim the &lt;strong&gt;TL;DR&lt;/strong&gt; boxes if you only have ten minutes; the &lt;strong&gt;Pitfall&lt;/strong&gt; boxes for mistakes we watch teams stumble into; the &lt;strong&gt;From the audit floor&lt;/strong&gt; boxes for the bug classes we have actually found in production code, anonymized but real.&lt;/p&gt;
&lt;p&gt;The topics are what you would expect: choosing primitives, when hybrid constructions are worth their cost (KEMs yes, signatures usually no), TLS and PKI migration, secure messaging, the library landscape, conformance testing with &lt;a href=&#34;https://symbolic.software/blog/2026-03-23-crucible/&#34;&gt;Crucible&lt;/a&gt;, rollout strategy, and a closing gallery of bug classes. None of it is new research. Most of it is the stuff our clients keep wishing someone had handed them at the start.&lt;/p&gt;
&lt;h2 id=&#34;the-companion-scorecard&#34;&gt;The Companion Scorecard&lt;/h2&gt;
&lt;p&gt;We also built a small site to go with it: &lt;strong&gt;&lt;a href=&#34;https://pq-migration.symbolic.software&#34;&gt;pq-migration.symbolic.software&lt;/a&gt;&lt;/strong&gt;. Twelve questions, three minutes, no signup, and you get a verdict across six dimensions of post-quantum readiness — plus a one-host TLS scanner that checks what your endpoint &lt;em&gt;actually&lt;/em&gt; negotiates, in case it doesn&amp;rsquo;t match what your team thinks.&lt;/p&gt;
&lt;p&gt;If you read the playbook and want to talk about your specific system, &lt;a href=&#34;https://symbolic.software/about&#34;&gt;get in touch&lt;/a&gt;. The whole reason this is a guide and not a checklist is that the right answers depend on context.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&#34;https://pq-migration.symbolic.software/static/pdf/playbook.pdf&#34;&gt;Download the playbook (PDF, 52 pages)&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&#34;https://pq-migration.symbolic.software&#34;&gt;Try the scorecard&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded>
      <category>Research</category>
      <category>Post-Quantum</category>
      
    </item>
    
    <item>
      <title>Verifpal 0.51.0: Sharper Semantics for Passwords, Assertions, and AEAD</title>
      <link>https://symbolic.software/blog/2026-04-20-verifpal-0-51/</link>
      <pubDate>Mon, 20 Apr 2026 00:00:00 &#43;0000</pubDate>
      <guid>https://symbolic.software/blog/2026-04-20-verifpal-0-51/</guid>
      <description>Verifpal 0.51.0 tightens the analysis engine&#39;s treatment of password-qualified values, checked ASSERT? assertions, and AEAD associated data, all on the back of excellent bug reports from the community.</description>
      <content:encoded>&lt;p&gt;Verifpal 0.51.0 is out. Where &lt;a href=&#34;https://symbolic.software/blog/2026-03-01-verifpal-engine-redesign/&#34;&gt;0.50.0&lt;/a&gt; was a deep architectural redesign of the analysis engine, 0.51.0 is a short, correctness-focused release: three semantic fixes, one UI fix, and a few new models — all driven by bug reports filed against the 0.50.0 series. None of the changes affect the Verifpal modeling language. Every existing model still parses, still runs, and produces results that are at worst the same and in several cases strictly more accurate.&lt;/p&gt;
&lt;p&gt;Thanks to &lt;strong&gt;Cédric Picard (&lt;a href=&#34;https://github.com/cym13&#34;&gt;@cym13&lt;/a&gt;)&lt;/strong&gt; for issues &lt;a href=&#34;https://github.com/symbolicsoft/verifpal/issues/11&#34;&gt;#11&lt;/a&gt;, &lt;a href=&#34;https://github.com/symbolicsoft/verifpal/issues/12&#34;&gt;#12&lt;/a&gt;, and &lt;a href=&#34;https://github.com/symbolicsoft/verifpal/issues/13&#34;&gt;#13&lt;/a&gt;, and to &lt;strong&gt;&lt;a href=&#34;https://github.com/EFCCWEB3&#34;&gt;@EFCCWEB3&lt;/a&gt;&lt;/strong&gt; for issue &lt;a href=&#34;https://github.com/symbolicsoft/verifpal/issues/10&#34;&gt;#10&lt;/a&gt;. This release exists because of them.&lt;/p&gt;
&lt;h2 id=&#34;passwords-from-static-tables-to-verifiable-guesses&#34;&gt;Passwords: From Static Tables to Verifiable Guesses&lt;/h2&gt;
&lt;p&gt;The largest change in 0.51.0 is a rewrite of how Verifpal reasons about &lt;code&gt;password&lt;/code&gt;-qualified values. Passwords occupy a strange place in the Verifpal model: they are secrets, but they are &lt;em&gt;low-entropy&lt;/em&gt; secrets, so the attacker is assumed to be able to brute-force them &lt;em&gt;if and only if&lt;/em&gt; the attacker has a way to verify guesses. Getting that &amp;ldquo;if and only if&amp;rdquo; right turns out to be subtle.&lt;/p&gt;
&lt;h3 id=&#34;what-was-wrong&#34;&gt;What Was Wrong&lt;/h3&gt;
&lt;p&gt;Before 0.51.0, password protection was defined by a static &lt;code&gt;password_hashing&lt;/code&gt; field on each primitive&amp;rsquo;s spec — a list of argument positions at which passwords were considered cryptographically protected. For &lt;code&gt;ENC&lt;/code&gt;, that list was &lt;code&gt;vec![1]&lt;/code&gt;: the plaintext is protected, the key is not.&lt;/p&gt;
&lt;p&gt;This captures the common case — if you encrypt a password with a known key, the attacker can brute-force it — but it fails on compositions. Consider:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;principal Alice[
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    knows private key
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    knows public  data
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    knows password pwd_2
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    msg2 = ENC(key, CONCAT(pwd_2, data))
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In 0.50.0, a passive confidentiality query on &lt;code&gt;pwd_2&lt;/code&gt; would &lt;strong&gt;fail&lt;/strong&gt;. The password sits inside &lt;code&gt;CONCAT&lt;/code&gt;, which has no &lt;code&gt;password_hashing&lt;/code&gt; list of its own, so the engine treated the password as &amp;ldquo;unprotected&amp;rdquo; and assumed the attacker could brute-force it — even though the attacker does not know &lt;code&gt;key&lt;/code&gt; and therefore has no way to verify a guess. This was Cédric&amp;rsquo;s &lt;a href=&#34;https://github.com/symbolicsoft/verifpal/issues/11&#34;&gt;issue #11&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The same shape arose elsewhere: &lt;code&gt;ENC(secret, pwd)&lt;/code&gt;, &lt;code&gt;MAC(secret, pwd)&lt;/code&gt;, &lt;code&gt;AEAD_ENC(pwd, secret, pubdata)&lt;/code&gt;, &lt;code&gt;BLIND(secret, pwd)&lt;/code&gt;, &lt;code&gt;SIGN(pwd, secret)&lt;/code&gt;. Each one has a different &amp;ldquo;right answer&amp;rdquo; depending on whether the attacker actually has enough of the surrounding context to mount an offline dictionary attack. A flat static table cannot express that.&lt;/p&gt;
&lt;h3 id=&#34;the-new-model&#34;&gt;The New Model&lt;/h3&gt;
&lt;p&gt;0.51.0 replaces the table with a dynamic, position-sensitive check. A &lt;code&gt;password&lt;/code&gt;-qualified value at position &lt;em&gt;i&lt;/em&gt; of primitive &lt;code&gt;P(a₀, …, aₙ)&lt;/code&gt; is obtainable by the attacker only when &lt;strong&gt;both&lt;/strong&gt; of the following hold at every primitive level in the nesting chain:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;No inherent protection.&lt;/strong&gt; Position &lt;em&gt;i&lt;/em&gt; is not listed in &lt;code&gt;P.password_hashing&lt;/code&gt;. This field is now reserved exclusively for primitives that resist brute-force by construction (only &lt;code&gt;PW_HASH&lt;/code&gt; qualifies — it is expensive by design). Every other primitive loses its static entry.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Verifiable guess.&lt;/strong&gt; The attacker knows every sibling argument &lt;code&gt;aⱼ&lt;/code&gt; (j ≠ i), which is what it takes to reconstruct &lt;code&gt;P(…, guess, …)&lt;/code&gt; and compare against the observed output. If any sibling is unknown, the guess cannot be verified and the password is safe.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Protection — check (1) — propagates to descendants: once you are inside a &lt;code&gt;PW_HASH&lt;/code&gt; argument, everything below is protected. Verifiability — check (2) — propagates only as long as every ancestor has known siblings; the first ancestor with an unknown sibling blocks the check all the way down.&lt;/p&gt;
&lt;p&gt;This gives much more accurate results across the matrix of primitives. For instance, in the canonical &lt;code&gt;ENC(key, pwd)&lt;/code&gt; case where &lt;code&gt;key&lt;/code&gt; is private, the attacker does not know &lt;code&gt;key&lt;/code&gt; — the only sibling of &lt;code&gt;pwd&lt;/code&gt; — so the password is safe. For &lt;code&gt;ENC(pwd, pubdata)&lt;/code&gt; where &lt;code&gt;pubdata&lt;/code&gt; is public, the attacker knows every sibling of &lt;code&gt;pwd&lt;/code&gt;, so the password is guessable. Both outcomes fall out of the same rule, with no per-primitive configuration.&lt;/p&gt;
&lt;p&gt;The new &lt;code&gt;examples/test/password_underspec.vp&lt;/code&gt; file walks through eleven queries across &lt;code&gt;ENC&lt;/code&gt;, &lt;code&gt;MAC&lt;/code&gt;, &lt;code&gt;AEAD_ENC&lt;/code&gt;, &lt;code&gt;SIGN&lt;/code&gt;, &lt;code&gt;BLIND&lt;/code&gt;, and &lt;code&gt;SHAMIR_SPLIT&lt;/code&gt;, with an inline comment on each one explaining what the attacker can and cannot do and why. It is probably the fastest way to get intuition for the new semantics.&lt;/p&gt;
&lt;p&gt;This fix addresses issue #11 in full and substantially refactors the machinery that &lt;a href=&#34;https://github.com/symbolicsoft/verifpal/issues/12&#34;&gt;issue #12&lt;/a&gt; was asking to clarify. #12 remains open for further discussion of edge cases, but the core formalism is now in place.&lt;/p&gt;
&lt;h2 id=&#34;checked-assert-now-actually-halts-the-principal&#34;&gt;Checked &lt;code&gt;ASSERT?&lt;/code&gt; Now Actually Halts the Principal&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/symbolicsoft/verifpal/issues/13&#34;&gt;Issue #13&lt;/a&gt;, also from Cédric, was a pleasing little bug. Consider a protocol where Alice checks a tag before leaking a derived value:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;principal Alice[
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    tag_ = DEC(Ka, enctag)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Kab_ = DEC(Ka, Kab_alice)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Kab_bob_ = DEC(Ka, Kab_bob_alice)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    _ = ASSERT(tag_, HASH(Kab_, Kab_bob_))?
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    leaks Kab_bob_
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;?&lt;/code&gt; on the &lt;code&gt;ASSERT&lt;/code&gt; makes it a &lt;em&gt;checked&lt;/em&gt; assertion: if the two sides are not equivalent, the principal halts, and the &lt;code&gt;leaks&lt;/code&gt; statement that follows does not execute. Any downstream reasoning about what the attacker can learn must respect that halt.&lt;/p&gt;
&lt;p&gt;In 0.50.0, Verifpal reported a false-positive attack against this protocol. An active attacker could swap two ciphertexts on the wire, making the assertion symbolically fail — the trace literally read &lt;code&gt;ASSERT(HASH(kab, ENC(kb, kab)), HASH(kab, kab))?&lt;/code&gt;, which is not equivalent under any substitution — and yet the engine still treated &lt;code&gt;leaks Kab_bob_&lt;/code&gt; as firing in that branch and harvested values from it. The attack trace was nonsensical: the assertion it relied on was clearly false, but Verifpal was claiming the attacker had won anyway.&lt;/p&gt;
&lt;p&gt;The underlying issue was that &lt;code&gt;leaks&lt;/code&gt; was represented as a single &lt;code&gt;leaked: bool&lt;/code&gt; flag on the target constant, with no record of which principal had declared the leak or where in that principal&amp;rsquo;s execution it sat. Active-attacker analysis can truncate a principal&amp;rsquo;s state after a failed checked primitive, but a constant declared &lt;em&gt;before&lt;/em&gt; the failed assertion is still in the truncated state — and the attacker was using the leaked-constant token to re-resolve that constant in the mutated state and pick up a value the principal never actually leaked.&lt;/p&gt;
&lt;p&gt;0.51.0 fixes this by adding an explicit &lt;code&gt;LeakEvent&lt;/code&gt; record for every &lt;code&gt;leaks&lt;/code&gt; declaration:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-rust&#34; data-lang=&#34;rust&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;LeakEvent&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;constant_id&lt;/span&gt;: &lt;span class=&#34;nc&#34;&gt;ValueId&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;principal_id&lt;/span&gt;: &lt;span class=&#34;nc&#34;&gt;PrincipalId&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;declared_at&lt;/span&gt;: &lt;span class=&#34;kt&#34;&gt;i32&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;phase&lt;/span&gt;: &lt;span class=&#34;kt&#34;&gt;i32&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Each &lt;code&gt;PrincipalState&lt;/code&gt; now carries an &lt;code&gt;Option&amp;lt;halted_at&amp;gt;&lt;/code&gt; stamp set whenever a checked primitive fails during mutation analysis. The &lt;code&gt;rule_equivalize&lt;/code&gt; deduction rule — the rule that lets the attacker re-resolve a known constant in the current state — consults both: if a leak event belongs to this principal with &lt;code&gt;declared_at &amp;gt; halted_at&lt;/code&gt;, the principal never reached it, and the equivalize step is suppressed.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;examples/test/assert_junglegym.vp&lt;/code&gt; model is the reduced test case from Cédric&amp;rsquo;s report, now enforced as a regression test. Behavior with unchecked &lt;code&gt;ASSERT&lt;/code&gt; (no &lt;code&gt;?&lt;/code&gt;) is deliberately unchanged: unchecked assertions are informative only, and the attacker is free to walk past them.&lt;/p&gt;
&lt;h2 id=&#34;aead_enc-no-longer-leaks-associated-data&#34;&gt;AEAD_ENC No Longer Leaks Associated Data&lt;/h2&gt;
&lt;p&gt;Up until 0.50.0, &lt;code&gt;AEAD_ENC(key, plaintext, ad)&lt;/code&gt; was declared with &lt;code&gt;passive_reveal: vec![2]&lt;/code&gt;, meaning a passive attacker watching the ciphertext on the wire was automatically given the associated data. This was wrong in two ways.&lt;/p&gt;
&lt;p&gt;First, the AD is an &lt;em&gt;input&lt;/em&gt; to AEAD encryption, not a part of the ciphertext output. It is bound into the authentication tag, but the tag commits to AD without necessarily revealing it — whether AD is public depends on how the protocol transmits it, not on the encryption primitive itself.&lt;/p&gt;
&lt;p&gt;Second, the blanket passive reveal was blocking legitimate queries where the AD was a secret (for example, a password used as AD), because Verifpal was automatically handing that secret to the attacker.&lt;/p&gt;
&lt;p&gt;0.51.0 removes the &lt;code&gt;passive_reveal: vec![2]&lt;/code&gt; entry. Associated data is now treated like any other argument: if the protocol sends it in the clear, the attacker sees it; if not, the attacker does not. The &lt;code&gt;aead_leak.vp&lt;/code&gt; test has been updated to reflect the corrected semantics, and a new &lt;code&gt;password_aead.vp&lt;/code&gt; test demonstrates that a password used as AD is safe when the other inputs are secret.&lt;/p&gt;
&lt;h2 id=&#34;tui-multi-output-primitive-labels&#34;&gt;TUI: Multi-Output Primitive Labels&lt;/h2&gt;
&lt;p&gt;Issue #10, from @EFCCWEB3, was a small but annoying display bug in the terminal UI. The compact deduction ticker strips a &lt;code&gt;&amp;quot;Output of &amp;quot;&lt;/code&gt; prefix off each deduction message to show a short label like &lt;code&gt;HASH via reconstruct&lt;/code&gt;. For &lt;em&gt;multi-output&lt;/em&gt; primitives — &lt;code&gt;HKDF&lt;/code&gt;, &lt;code&gt;SPLIT&lt;/code&gt;, &lt;code&gt;SHAMIR_SPLIT&lt;/code&gt; — the underlying message uses indexed forms like &lt;code&gt;&amp;quot;First output of HKDF(…)&amp;quot;&lt;/code&gt; or &lt;code&gt;&amp;quot;Second output of SPLIT(…)&amp;quot;&lt;/code&gt;. The literal-prefix match missed those, and the ticker fell back to the first whitespace-separated word, which was &lt;code&gt;&amp;quot;First&amp;quot;&lt;/code&gt; or &lt;code&gt;&amp;quot;Second&amp;quot;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The fix is one line: look for the shared &lt;code&gt;&amp;quot; of &amp;quot;&lt;/code&gt; delimiter instead of the literal prefix. Multi-output primitives now show up correctly in the ticker.&lt;/p&gt;
&lt;h2 id=&#34;new-models&#34;&gt;New Models&lt;/h2&gt;
&lt;p&gt;Two models of note ship with the release:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;examples/transport-layer/tls13.vp&lt;/code&gt;&lt;/strong&gt; — a 326-line TLS 1.3 model, included explicitly as a case study in Verifpal&amp;rsquo;s current limitations on large multi-phase protocols with many key-schedule stages. It is useful both as a reference and as motivation for future engine work.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;examples/messaging/scuttlebutt.vp&lt;/code&gt;&lt;/strong&gt; — the existing Scuttlebutt model has been updated to guard &lt;code&gt;longTermBPub&lt;/code&gt; (&lt;code&gt;Bob -&amp;gt; Alice: [longTermBPub]&lt;/code&gt;), which exercises more interesting branches of the analysis than the unguarded form.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The password, assertion, and AEAD fixes each ship with at least one new dedicated test model in &lt;code&gt;examples/test/&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&#34;getting-verifpal-0510&#34;&gt;Getting Verifpal 0.51.0&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Scoop (Windows):&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;scoop update verifpal
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Homebrew (macOS, Linux):&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;brew upgrade verifpal
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;From source:&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;cargo install --path . --features cli
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Binaries for Windows, Linux, macOS, and FreeBSD are attached to the &lt;a href=&#34;https://github.com/symbolicsoft/verifpal/releases/tag/v0.51.0&#34;&gt;0.51.0 GitHub release&lt;/a&gt;. As always, issues and contributions are welcome at &lt;a href=&#34;https://github.com/symbolicsoft/verifpal&#34;&gt;github.com/symbolicsoft/verifpal&lt;/a&gt;.&lt;/p&gt;
</content:encoded>
      <category>Software</category>
      <category>Research</category>
      <category>Verifpal</category>
      <category>Formal Verification</category>
      
    </item>
    
    <item>
      <title>Hybrid Constructions Are a Safety Blanket, and That&#39;s Fine</title>
      <link>https://symbolic.software/blog/2026-04-13-hybrid-constructions/</link>
      <pubDate>Mon, 13 Apr 2026 00:00:00 &#43;0000</pubDate>
      <guid>https://symbolic.software/blog/2026-04-13-hybrid-constructions/</guid>
      <description>Why Symbolic Software agrees with Soatok&#39;s position on hybrid post-quantum constructions: hybrids are compelling for KEMs, far less necessary for signatures, and the real risk is migration friction.</description>
      <content:encoded>&lt;p&gt;Soatok published a &lt;a href=&#34;https://soatok.blog/2026/04/13/hybrid-constructions-the-post-quantum-safety-blanket/&#34;&gt;clear-eyed piece today&lt;/a&gt; on hybrid post-quantum constructions&amp;mdash;the practice of combining a classical algorithm (X25519, ECDSA) with a post-quantum one (ML-KEM, ML-DSA) so that the system remains secure even if one of the two is broken. The post argues that hybrid KEMs are a reasonable safety blanket while hybrid signatures have a weaker justification, and that the industry should stop letting the perfect be the enemy of the deployed. We broadly agree, and this post explains why.&lt;/p&gt;
&lt;h2 id=&#34;the-asymmetry-between-kems-and-signatures&#34;&gt;The Asymmetry Between KEMs and Signatures&lt;/h2&gt;
&lt;p&gt;Soatok&amp;rsquo;s central observation is one that deserves wider uptake: the harvest-now-decrypt-later (HNDL) threat that motivates hybrid KEMs has no analogue for signatures.&lt;/p&gt;
&lt;p&gt;When an adversary captures ciphertext today, they can store it indefinitely and decrypt it once a cryptographically relevant quantum computer (CRQC) arrives. The data that was confidential in 2026 is still confidential in 2036. A hybrid KEM like &lt;a href=&#34;https://www.ietf.org/archive/id/draft-connolly-cfrg-xwing-kem-06.html&#34;&gt;X-Wing&lt;/a&gt;&amp;mdash;which combines ML-KEM-768 with X25519&amp;mdash;hedges against this threat by ensuring that an attacker must break &lt;em&gt;both&lt;/em&gt; the lattice problem and the elliptic curve discrete logarithm to recover the plaintext. If ML-KEM turns out to be flawed, X25519 is still there. If X25519 falls to a quantum adversary, ML-KEM is still there. The cost is modest: a slightly larger key exchange, a second KDF call, and some additional bandwidth. The insurance is real.&lt;/p&gt;
&lt;p&gt;Signatures are different. A signature is verified at the time it is received. A quantum adversary who arrives in 2036 cannot retroactively forge a signature that was verified and acted upon in 2026. There is no stockpile of signatures waiting to be broken. The threat model that justifies hybrid KEMs&amp;mdash;store now, exploit later&amp;mdash;does not apply in the same way.&lt;/p&gt;
&lt;p&gt;That said, hybrid signatures are not without merit. Long-lived signatures on software packages, certificates, or firmware could in principle be forged by a future quantum adversary, and there is a reasonable belt-and-suspenders argument for hedging against a lattice break even in the authentication context. The case is simply less urgent than for KEMs, where the consequences of a wrong bet are retroactive and irrecoverable.&lt;/p&gt;
&lt;p&gt;We have been &lt;a href=&#34;https://symbolic.software/blog/2026-04-02-pq-native/&#34;&gt;recommending post-quantum native design&lt;/a&gt; for all new systems since April 2, with hybrid constructions where backward compatibility requires them. That recommendation was deliberately agnostic about signatures versus KEMs. We are now refining it: &lt;strong&gt;hybrid KEMs are a compelling transitional measure; hybrid signatures are far less necessary, and should be evaluated against the specific threat model rather than adopted as a default.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;lattice-confidence-is-earned-not-assumed&#34;&gt;Lattice Confidence Is Earned, Not Assumed&lt;/h2&gt;
&lt;p&gt;A recurring objection to pure post-quantum deployment is that lattice-based cryptography is &amp;ldquo;too new&amp;rdquo; to trust without a classical fallback. Soatok addresses this directly, and the numbers bear repeating: NTRU was patented in 1997. The learning-with-errors problem was introduced by Regev in 2005&amp;mdash;the same year Curve25519 was published. ML-KEM and ML-DSA survived nearly a decade of the most intensive public cryptanalysis effort the field has ever organized, the NIST Post-Quantum Cryptography Standardization Process, in which hundreds of researchers from dozens of countries actively tried to break every candidate.&lt;/p&gt;
&lt;p&gt;The process worked. SIKE, the supersingular isogeny-based scheme, was broken during the competition&amp;mdash;a catastrophic attack that the evaluation process was designed to surface. The lattice candidates survived. This is not absence of scrutiny; it is scrutiny that produced a result.&lt;/p&gt;
&lt;p&gt;We have additional evidence from our own work. When we built &lt;a href=&#34;https://symbolic.software/blog/2026-03-23-crucible/&#34;&gt;Crucible&lt;/a&gt; and tested 15 ML-KEM and ML-DSA implementations across five languages, we found two minor conformance gaps and zero security vulnerabilities. The implementations that ship inside AWS-LC, Cloudflare CIRCL, the Go standard library, and wolfSSL&amp;rsquo;s FIPS module all passed every test. The post-quantum ecosystem is not speculative anymore. It is production-grade software that has been tested, audited, and deployed at scale.&lt;/p&gt;
&lt;h2 id=&#34;the-real-risk-is-complexity&#34;&gt;The Real Risk Is Complexity&lt;/h2&gt;
&lt;p&gt;Here is what our audit practice has taught us: the most common source of cryptographic failure is not a broken primitive. It is the complexity surrounding the primitive&amp;mdash;the state machine that manages keys, the serialization layer that encodes messages, the fallback logic that negotiates which algorithm to use.&lt;/p&gt;
&lt;p&gt;Every hybrid construction doubles the number of moving parts in exactly these layers. A hybrid KEM requires two key generations, two encapsulations, two decapsulations, and a combiner that must be correctly implemented to preserve the security properties of both components. A hybrid signature requires two signing operations, two verification operations, and a composition that must not introduce verification oracle attacks. Each additional algorithm is additional attack surface&amp;mdash;not because the algorithm itself is weak, but because the &lt;em&gt;integration&lt;/em&gt; is where bugs live.&lt;/p&gt;
&lt;p&gt;We have spent the past two months &lt;a href=&#34;https://symbolic.software/blog/2026-02-05-cryspen/&#34;&gt;documenting&lt;/a&gt; what happens when cryptographic implementation complexity outpaces engineering discipline. The bugs we found were not in the cryptographic primitives. They were in the wrappers, the serialization, the specification-to-code translation, the build system defaults. Adding a second algorithm to every cryptographic operation in a system multiplies exactly these surfaces.&lt;/p&gt;
&lt;p&gt;For KEMs, the HNDL threat clearly justifies this cost. The insurance against a catastrophic retroactive loss of confidentiality is worth the additional integration complexity. For signatures, the calculus is less clear-cut. Hybrid signatures carry the same complexity costs, but the threat they hedge against&amp;mdash;a lattice break that enables forgery&amp;mdash;is one where the damage is prospective rather than retroactive. That does not make hybrid signatures pointless; it makes them a less urgent priority than hybrid KEMs, and one where the complexity tradeoff deserves more scrutiny.&lt;/p&gt;
&lt;h2 id=&#34;migration-friction-kills&#34;&gt;Migration Friction Kills&lt;/h2&gt;
&lt;p&gt;Soatok makes a pragmatic point that resonates with everything we see in practice: the biggest risk to the post-quantum transition is not that the algorithms are wrong. It is that the transition takes too long because the industry overcomplicates it.&lt;/p&gt;
&lt;p&gt;Every additional requirement&amp;mdash;hybrid signatures on top of hybrid KEMs, dual certificate chains, negotiation for four algorithms instead of two&amp;mdash;adds friction to migration. Friction means slower adoption. Slower adoption means more years of classical-only systems exposed to the HNDL threat that the entire transition is supposed to address. The irony is acute: treating hybrid signatures as a prerequisite for deployment may extend the window during which systems are vulnerable to exactly the quantum threat they are trying to mitigate.&lt;/p&gt;
&lt;p&gt;Our &lt;a href=&#34;https://symbolic.software/blog/2026-04-02-pq-native/&#34;&gt;recommendation&lt;/a&gt; stands: design new systems as post-quantum native. Use ML-KEM for key encapsulation. Use ML-DSA for digital signatures. Use hybrid KEMs (X-Wing or equivalent) where backward compatibility with classical systems is required during transition. Hybrid signatures may be appropriate for specific use cases&amp;mdash;long-lived code signing, certificate authorities, scenarios where signature validity must span decades&amp;mdash;but they should not be the default, and they should not gate the broader migration.&lt;/p&gt;
&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Soatok&amp;rsquo;s post captures something the applied cryptography community has been circling around without stating plainly: hybrid constructions are a safety blanket, and for KEMs, that blanket is clearly worth the cost. For signatures, the case is real but less compelling. The distinction is not about confidence in lattices&amp;mdash;it is about whether the urgency of the threat model justifies the additional complexity.&lt;/p&gt;
&lt;p&gt;We are a team that has audited more post-quantum implementations than most. We built the &lt;a href=&#34;https://symbolic.software/blog/2026-03-23-crucible/&#34;&gt;conformance testing framework&lt;/a&gt; that the ecosystem uses to validate them. We &lt;a href=&#34;https://symbolic.software/blog/2026-04-07-cryspen-hax/&#34;&gt;documented&lt;/a&gt; what happens when verification complexity outpaces engineering reality. From that vantage point: the lattice primitives are sound, the implementations are maturing, and the greatest risk to the transition is making it harder than it needs to be.&lt;/p&gt;
&lt;p&gt;Deploy post-quantum cryptography. Use hybrid KEMs where they make sense. Consider hybrid signatures where the use case warrants them, but do not treat them as a prerequisite. And above all, do not let the pursuit of a theoretically perfect transition delay the practically necessary one.&lt;/p&gt;
&lt;p&gt;The full version of this argument, alongside the rest of our migration recommendations, lives in our &lt;a href=&#34;https://pq-migration.symbolic.software/static/pdf/playbook.pdf&#34;&gt;Post-Quantum Migration Playbook&lt;/a&gt; — a free practitioner guide covering primitives, hybrid constructions, TLS migration, libraries, conformance testing, rollout strategy, and the bug classes we keep finding in audits. Companion to our live &lt;a href=&#34;https://pq-migration.symbolic.software&#34;&gt;PQ Migration Readiness scorecard and scanner&lt;/a&gt;.&lt;/p&gt;
</content:encoded>
      <category>Research</category>
      <category>Post-Quantum</category>
      
    </item>
    
    <item>
      <title>The Verification Facade: Structural Gaps in Cryspen&#39;s Hax Pipeline</title>
      <link>https://symbolic.software/blog/2026-04-07-cryspen-hax/</link>
      <pubDate>Tue, 07 Apr 2026 00:00:00 &#43;0000</pubDate>
      <guid>https://symbolic.software/blog/2026-04-07-cryspen-hax/</guid>
      <description>Five proof-of-concept exploits against ML-DSA, ML-KEM, Ed25519, and ChaCha20 demonstrate three classes of semantic gap in hax&#39;s Rust-to-F* extraction pipeline, where verified models diverge from deployed code.</description>
      <content:encoded>




&lt;nav class=&#34;series-nav&#34; aria-label=&#34;Cryspen series (top)&#34;&gt;
  &lt;header class=&#34;series-nav-header&#34;&gt;
    &lt;span class=&#34;series-nav-label&#34;&gt;▶ Cryspen Series&lt;/span&gt;
    &lt;span class=&#34;series-nav-count&#34;&gt;5 posts · 2 papers · 1 talk&lt;/span&gt;
  &lt;/header&gt;
  &lt;ol class=&#34;series-nav-list&#34;&gt;
    
    
    &lt;li&gt;
      
      &lt;a href=&#34;https://symbolic.software/blog/2026-02-05-cryspen/&#34;&gt;On the Promises of &amp;#39;High-Assurance&amp;#39; Cryptography&lt;/a&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.02.05&lt;/span&gt;
    &lt;/li&gt;
    
    
    &lt;li&gt;
      
      &lt;a href=&#34;https://symbolic.software/blog/2026-02-12-cryspen-response/&#34;&gt;We Found Bugs in Cryspen&amp;#39;s Verified Code&lt;/a&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.02.12&lt;/span&gt;
    &lt;/li&gt;
    
    
    &lt;li&gt;
      
      &lt;a href=&#34;https://symbolic.software/blog/2026-02-17-cryspen-mldsa/&#34;&gt;Even More Bugs in Cryspen&amp;#39;s libcrux: ML-DSA&lt;/a&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.02.17&lt;/span&gt;
    &lt;/li&gt;
    
    
    &lt;li&gt;
      
      &lt;a href=&#34;https://symbolic.software/blog/2026-03-07-cryspen-tls/&#34;&gt;Cryspen&amp;#39;s Approach to TLS: A Critical Analysis&lt;/a&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.03.07&lt;/span&gt;
    &lt;/li&gt;
    
    
    &lt;li class=&#34;current&#34; aria-current=&#34;page&#34;&gt;
      
      &lt;span class=&#34;title&#34;&gt;The Verification Facade: Structural Gaps in Hax&lt;/span&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.04.07&lt;/span&gt;
    &lt;/li&gt;
    
  &lt;/ol&gt;
  &lt;div class=&#34;series-nav-footer&#34;&gt;
    &lt;div class=&#34;series-nav-row&#34;&gt;
      &lt;span class=&#34;series-nav-rowlabel&#34;&gt;Papers&lt;/span&gt;
      &lt;a href=&#34;https://eprint.iacr.org/2026/192&#34;&gt;&lt;em&gt;Verification Theatre&lt;/em&gt;&lt;/a&gt;
      &lt;span class=&#34;series-nav-sep&#34;&gt;·&lt;/span&gt;
      &lt;a href=&#34;https://eprint.iacr.org/2026/670&#34;&gt;&lt;em&gt;Verification Facade&lt;/em&gt;&lt;/a&gt;
    &lt;/div&gt;
    &lt;div class=&#34;series-nav-row&#34;&gt;
      &lt;span class=&#34;series-nav-rowlabel&#34;&gt;Talk&lt;/span&gt;
      &lt;a href=&#34;https://www.youtube.com/watch?v=TdOXza1-M_4&#34;&gt;&lt;em&gt;High Assurance Cryptography and the Ethics of Disclosure (OSTIF)&lt;/em&gt;&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/nav&gt;

&lt;p&gt;Over the past two months, we have documented &lt;a href=&#34;https://symbolic.software/blog/2026-03-07-cryspen-tls/&#34;&gt;sixteen vulnerabilities&lt;/a&gt; across six Cryspen projects&amp;mdash;bugs in unverified wrappers, wrong formal specifications, false proofs, and unsound axioms. Those findings concerned the &lt;em&gt;product&lt;/em&gt;. We now turn to the &lt;em&gt;toolchain&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Today, we&amp;rsquo;re publishing our latest paper, &lt;a href=&#34;https://eprint.iacr.org/2026/670&#34;&gt;&lt;em&gt;Verification Facade: Masquerading Insecure Cryptographic Implementations as Verified Code&lt;/em&gt;&lt;/a&gt;, which examines &lt;a href=&#34;https://github.com/cryspen/hax&#34;&gt;Hax&lt;/a&gt;, the verification pipeline that underlies Cryspen&amp;rsquo;s &amp;ldquo;formally verified&amp;rdquo; claims.&lt;/p&gt;
&lt;p&gt;Hax translates a subset of Rust into F*, enabling machine-checked proofs of panic freedom and functional correctness. ML-KEM and ML-DSA implementations verified via hax are being developed in partnership with Google and tested in Signal&amp;rsquo;s PQXDH protocol. If hax&amp;rsquo;s translation is faithful, the F* proofs transfer to the Rust code. If it is not, the proofs verify a model that diverges from what actually executes.&lt;/p&gt;
&lt;p&gt;We find that it is not&amp;mdash;in three distinct ways. We identify three classes of semantic gap in hax&amp;rsquo;s pipeline and demonstrate each through proof-of-concept exploits against ML-DSA, ML-KEM, Ed25519, and ChaCha20. Every exploit must pass all four of our inclusion gates:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Correct Rust&lt;/strong&gt;: the code compiles as valid Rust.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Successful extraction&lt;/strong&gt;: hax extracts it to F* without errors or warnings.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Security-relevant model divergence&lt;/strong&gt;: the verified F* model diverges from the deployed Rust code in a way that compromises a cryptographic security property.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Invisible to testing&lt;/strong&gt;: the divergence escapes detection by functional tests (sign-then-verify, encrypt-then-decrypt, test vectors all pass).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;All source code and pre-extracted F* output are &lt;a href=&#34;https://github.com/symbolicsoft/verification-facade&#34;&gt;available on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We call the resulting phenomenon a &lt;em&gt;verification facade&lt;/em&gt;: verification that is performed but covers less than it appears to cover. Unlike &lt;a href=&#34;https://eprint.iacr.org/2026/192&#34;&gt;verification theatre&lt;/a&gt;&amp;mdash;where verification is claimed but not performed&amp;mdash;a facade involves verification that &lt;em&gt;is&lt;/em&gt; performed but whose model either diverges from the deployed code, depends on unenforceable assumptions, or cannot express a security-critical property.&lt;/p&gt;
&lt;h2 id=&#34;how-hax-works&#34;&gt;How Hax Works&lt;/h2&gt;
&lt;p&gt;Hax translates Rust to F* through three stages. A &lt;strong&gt;frontend&lt;/strong&gt; hooks into &lt;code&gt;rustc&lt;/code&gt; and extracts the Typed High-Level Intermediate Representation (THIR)&amp;mdash;a representation where references are still present, borrow-checker constraints are implicit, and &lt;code&gt;Drop&lt;/code&gt; glue has not been elaborated. A &lt;strong&gt;transformation engine&lt;/strong&gt; then applies 35 sequential phases, each eliminating a language feature: by the time the AST reaches the backend, mutable variables, references, raw pointers, lifetimes, all loop forms, and the &lt;code&gt;?&lt;/code&gt; operator have been eliminated, leaving a purely functional representation. Finally, the &lt;strong&gt;F* backend&lt;/strong&gt; translates this to F* surface syntax. Integer types become refinement-typed wrappers (e.g., &lt;code&gt;u8&lt;/code&gt; becomes &lt;code&gt;int_t U8&lt;/code&gt; with range $[0,255]$). Rust&amp;rsquo;s &lt;code&gt;+&lt;/code&gt; becomes &lt;code&gt;+!&lt;/code&gt; (strict, with overflow proof obligation); &lt;code&gt;wrapping_add&lt;/code&gt; becomes &lt;code&gt;+.&lt;/code&gt; (modular).&lt;/p&gt;
&lt;p&gt;The pipeline&amp;rsquo;s feature-witness system ensures &lt;em&gt;structural&lt;/em&gt; correctness (the output has the right shape) but not &lt;em&gt;semantic&lt;/em&gt; preservation (the output has the same meaning). For hax&amp;rsquo;s verification claims to hold, three unverified components must be correct: the 35 OCaml transformation phases, the F* proof libraries (which axiomatize 113 operations via &lt;code&gt;assume val&lt;/code&gt;&amp;mdash;roughly 19% of the integer model), and user annotations (&lt;code&gt;#[opaque]&lt;/code&gt;, &lt;code&gt;assume!&lt;/code&gt;, &lt;code&gt;fstar::replace&lt;/code&gt;, &lt;code&gt;verification_status(lax)&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;For code that fits its extraction model&amp;mdash;deterministic &lt;code&gt;for&lt;/code&gt; loops over bounded ranges, pure arithmetic with explicit bounds, array transformations with statically known indices&amp;mdash;hax provides genuine verification. Every &lt;code&gt;+!&lt;/code&gt; and &lt;code&gt;*!&lt;/code&gt; generates a real overflow proof obligation. Bounded &lt;code&gt;for&lt;/code&gt; loops extract to a defined (not axiomatized) &lt;code&gt;fold_range&lt;/code&gt; combinator with real loop invariants. And &lt;code&gt;ensures&lt;/code&gt; clauses are proof obligations, not axioms: if the function body violates the postcondition, F* rejects the code. We confirmed this experimentally.&lt;/p&gt;
&lt;p&gt;The gaps arise elsewhere.&lt;/p&gt;
&lt;h2 id=&#34;the-gaps-and-their-exploits&#34;&gt;The Gaps and Their Exploits&lt;/h2&gt;
&lt;p&gt;We identify three classes of semantic gap and demonstrate each with proof-of-concept exploits. We distinguish three gradations: &lt;em&gt;facade gaps&lt;/em&gt; (E1, E3, E4), where the F* model actively diverges from Rust; a &lt;em&gt;conditional gap&lt;/em&gt; (E2), where the divergence depends on the compilation mode; and a &lt;em&gt;scope gap&lt;/em&gt; (E5), where the model is faithful but cannot cover a critical property.&lt;/p&gt;
&lt;h3 id=&#34;e1-ml-dsa-rejection-sampling-facade-gap&#34;&gt;E1: ML-DSA Rejection Sampling (Facade Gap)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;The gap: while-loop ghosting.&lt;/strong&gt; When a &lt;code&gt;while&lt;/code&gt; loop lacks a &lt;code&gt;loop_decreases!&lt;/code&gt; annotation, hax generates a fuel function returning constant 0. The F* &lt;code&gt;while_loop&lt;/code&gt; combinator requires fuel to strictly decrease on each iteration (&lt;code&gt;fuel(next) &amp;lt; fuel(current)&lt;/code&gt;)&amp;mdash;with constant fuel 0, this obligation becomes $0 &lt; 0$, which is $\mathsf{False}$. The loop body is &lt;em&gt;proof-inert&lt;/em&gt;: it exists syntactically but cannot support any verification claim.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The exploit.&lt;/strong&gt; ML-DSA signing (FIPS 204) uses rejection sampling: the signer computes $\mathbf{z} = \mathbf{y} + c\mathbf{s}_1$ and retries until $\|\mathbf{z}\|_\infty &lt; \gamma_1 - \beta$. This loop is what makes ML-DSA signatures zero-knowledge. Our implementation is straightforward:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-rust&#34; data-lang=&#34;rust&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;fn&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;sign_message&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;s1&lt;/span&gt;: &lt;span class=&#34;kp&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;nc&#34;&gt;SecretS1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;seed&lt;/span&gt;: &lt;span class=&#34;kt&#34;&gt;u64&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;-&amp;gt; &lt;span class=&#34;nc&#34;&gt;SignatureZ&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;mut&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;nonce&lt;/span&gt;: &lt;span class=&#34;kt&#34;&gt;u32&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;mut&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;z&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;generate_mask&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;seed&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;nonce&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;z&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;compute_z&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;z&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;s1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;while&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;check_norm_bound&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;z&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// rejection sampling
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;nonce&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;nonce&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;wrapping_add&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;z&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;generate_mask&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;seed&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;nonce&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;z&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;compute_z&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;z&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;s1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;z&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The code compiles, the rejection loop runs correctly, and sign-then-verify tests pass. But the extracted F* tells a different story:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-gdscript3&#34; data-lang=&#34;gdscript3&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;sign_message&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;s1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;t_SecretS1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;seed&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;u64&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; &lt;span class=&#34;n&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;z&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;compute_z&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;generate_mask&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;seed&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;s1&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;in&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; &lt;span class=&#34;n&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;nonce&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;z&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;Rust_primitives&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Hax&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;while_loop&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;   &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;fun&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;_&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&#34;bp&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;                        &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;invariant&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;   &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;fun&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;_&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;z&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;~.&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;check_norm_bound&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;z&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;condition&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;   &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;fun&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;_&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Int&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;from_machine&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;mk_u32&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;fuel&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;   &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;z&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;                                 &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;accumulator&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;   &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;fun&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;nonce&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;z&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;                     &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;body&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;nonce&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;wrapping_add&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;nonce&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;in&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;z&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;compute_z&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;generate_mask&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;seed&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;nonce&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;s1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;ow&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;nonce&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;z&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; &lt;span class=&#34;ow&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;z&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Line 7 is the problem. The fuel function returns constant 0, so the body&amp;rsquo;s refinement type requires $0 &lt; 0$&amp;mdash;impossible. Z3 cannot use the body to prove anything about how the accumulator changes. The Rust code correctly rejects candidates, but F* has no way to verify this.&lt;/p&gt;
&lt;p&gt;ML-DSA&amp;rsquo;s security relies on the Fiat-Shamir with Aborts paradigm: rejection sampling ensures that $\mathbf{z}$&amp;rsquo;s distribution is statistically independent of $\mathbf{s}_1$. Without this property, lattice techniques can recover the secret key from approximately 1000 signatures. The verification is supposed to cover exactly this&amp;mdash;and it cannot.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A stronger variant.&lt;/strong&gt; Because the &lt;code&gt;while&lt;/code&gt; loop is proof-inert, F* cannot distinguish a correct rejection loop from a &lt;em&gt;broken&lt;/em&gt; one. An adversarial developer could invert the loop condition&amp;mdash;&lt;code&gt;while check_norm_bound(&amp;amp;z)&lt;/code&gt; instead of &lt;code&gt;while !check_norm_bound(&amp;amp;z)&lt;/code&gt;&amp;mdash;causing the function to return the first candidate that &lt;em&gt;fails&lt;/em&gt; the norm bound, directly leaking $\mathbf{s}_1$. The extracted F* would be structurally identical: the same &lt;code&gt;while_loop&lt;/code&gt; call with the same fuel=0, and the same inability to prove anything about the output. A conforming verifier would eventually reject some of the resulting signatures (since FIPS 204 Algorithm 8 checks the same bound), so functional testing would catch it&amp;mdash;but F* provides no earlier warning. The verification facade is the same whether the underlying code is correct or broken.&lt;/p&gt;
&lt;h3 id=&#34;e2-ml-kem-barrett-overflow-conditional-gap&#34;&gt;E2: ML-KEM Barrett Overflow (Conditional Gap)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;The gap: deployment model divergence.&lt;/strong&gt; Hax translates Rust&amp;rsquo;s &lt;code&gt;+&lt;/code&gt; to F*&amp;rsquo;s &lt;code&gt;+!&lt;/code&gt; (strict, panics on overflow), matching debug-mode semantics. In release mode, overflow wraps silently instead.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The exploit.&lt;/strong&gt; Our proof-of-concept implements ML-KEM Barrett reduction with correct preconditions. The code compiles, passes debug-mode tests, and extracts to F* where arithmetic becomes strict. The F* proof establishes: &amp;ldquo;if the precondition holds, the result is correct; if not, the program panics.&amp;rdquo; But release mode introduces a third possibility&amp;mdash;precondition violated, no panic, silently wrong result&amp;mdash;that the F* model does not represent. Incorrect Barrett reductions corrupt NTT polynomial arithmetic, breaking ML-KEM&amp;rsquo;s IND-CCA security.&lt;/p&gt;
&lt;p&gt;This is a &lt;em&gt;conditional&lt;/em&gt; gap: a correct implementation that always reduces after each multiplication stays within bounds. The divergence requires a caller that skips intermediate reduction&amp;mdash;but such callers exist in the wild.&lt;/p&gt;
&lt;h3 id=&#34;e3-ed25519-clamping-facade-gap&#34;&gt;E3: Ed25519 Clamping (Facade Gap)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;The gap: assumption laundering.&lt;/strong&gt; &lt;code&gt;assume!(P)&lt;/code&gt; compiles to &lt;code&gt;()&lt;/code&gt; in Rust but introduces $P$ as an axiom in F*. A false assumption introduces $\mathsf{False}$ into the proof context, making every subsequent proof trivially satisfiable via &lt;em&gt;ex falso quodlibet&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The exploit.&lt;/strong&gt; Ed25519 clamps the secret scalar: clear bits 0&amp;ndash;2, clear bit 255, set bit 254. Our proof-of-concept introduces a subtle bug: &lt;code&gt;|&lt;/code&gt; (OR) instead of &lt;code&gt;&amp;amp;&lt;/code&gt; (AND), &lt;em&gt;setting&lt;/em&gt; bit 255 instead of clearing it. An &lt;code&gt;assume!&lt;/code&gt; call immediately after asserts the bit is clear:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-rust&#34; data-lang=&#34;rust&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;fn&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;clamp_scalar&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;secret&lt;/span&gt;: &lt;span class=&#34;kp&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;nc&#34;&gt;Scalar&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;-&amp;gt; &lt;span class=&#34;nc&#34;&gt;Scalar&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;mut&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;secret&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mh&#34;&gt;0xF0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;hax_lib&lt;/span&gt;::&lt;span class=&#34;fm&#34;&gt;assume!&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;((&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mh&#34;&gt;0x07&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;==&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// BUG: | 0x80 SETS bit 255 (should be &amp;amp; 0x7F)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;31&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;31&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mh&#34;&gt;0x80&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// assume! LIES: claims bit 255 is clear
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;hax_lib&lt;/span&gt;::&lt;span class=&#34;fm&#34;&gt;assume!&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;((&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;31&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mh&#34;&gt;0x80&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;==&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;31&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;31&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mh&#34;&gt;0x40&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;hax_lib&lt;/span&gt;::&lt;span class=&#34;fm&#34;&gt;assume!&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;((&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;31&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mh&#34;&gt;0x40&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In the extracted F*, line 2 computes &lt;code&gt;s[31] | 0x80&lt;/code&gt; (sets bit 255), while line 4 assumes &lt;code&gt;(s[31] &amp;amp; 0x80) = 0&lt;/code&gt; (bit 255 is clear). The contradiction introduces $\mathsf{False}$, and from that point forward every postcondition in the function &amp;ldquo;verifies&amp;rdquo; vacuously. The developer sees green checks; the verification is meaningless.&lt;/p&gt;
&lt;p&gt;The Rust code still produces valid Ed25519 signatures&amp;mdash;the curve math works for any scalar&amp;mdash;so sign-then-verify tests pass. Only tests that check the clamped scalar directly would catch this. With bit 255 set, the scalar may exceed the group order $L$, and the effective signing key becomes $s \bmod L$&amp;mdash;a different key than intended, enabling key-substitution attacks.&lt;/p&gt;
&lt;h3 id=&#34;e4-ml-kem-samplentt-facade-gap&#34;&gt;E4: ML-KEM SampleNTT (Facade Gap)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;The gap: while-loop ghosting, compounded.&lt;/strong&gt; This exploit combines the fuel=0 bug from E1 with a second problem: early &lt;code&gt;return&lt;/code&gt; inside the loop body forces hax to use &lt;code&gt;while_loop_return&lt;/code&gt;, an &lt;code&gt;assume val&lt;/code&gt; combinator with &lt;em&gt;no implementation at all&lt;/em&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;assume val while_loop_return #acc_t #ret_t
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  (inv: acc_t -&amp;gt; Type0)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  (condition: (c:acc_t {inv c}) -&amp;gt; bool)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  (fuel: (a:acc_t -&amp;gt; nat))
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  (init: acc_t)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  (f: (acc_t -&amp;gt; ControlFlow ... acc_t))
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  : ControlFlow ret_t acc_t
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;The exploit.&lt;/strong&gt; ML-KEM&amp;rsquo;s SampleNTT is a rejection sampling loop that accepts coefficients only if they are less than $q = 3329$. Our implementation includes an early &lt;code&gt;return None&lt;/code&gt; when randomness is exhausted. The extracted F* is doubly proof-inert: the combinator is an axiom Z3 cannot reason about, and the fuel is constant 0.&lt;/p&gt;
&lt;p&gt;The property &amp;ldquo;all sampled coefficients are less than $q$&amp;rdquo; is essential&amp;mdash;coefficients $\geq q$ break the ring structure on which ML-KEM&amp;rsquo;s security rests. F* cannot verify this property. Even a correct &lt;code&gt;loop_decreases!&lt;/code&gt; annotation would not help, because &lt;code&gt;while_loop_return&lt;/code&gt; has no body for Z3 to unfold.&lt;/p&gt;
&lt;h3 id=&#34;e5-chacha20-rotations-scope-gap&#34;&gt;E5: ChaCha20 Rotations (Scope Gap)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;The gap: proof library axioms.&lt;/strong&gt; The proof library axiomatizes operations that &lt;em&gt;could&lt;/em&gt; be defined&amp;mdash;including bit rotations&amp;mdash;via &lt;code&gt;assume val&lt;/code&gt; with no postconditions. F* knows only the return type, not what the function computes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The exploit.&lt;/strong&gt; ChaCha20 quarter rounds use left rotation by 16, 12, 8, and 7 bits. Our implementation is &lt;em&gt;correct&lt;/em&gt;&amp;mdash;it passes standard test vectors. The extracted F* shows the rotation amounts explicitly:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;impl_u32__rotate_left (state.[ d ]) (mk_u32 16)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;impl_u32__rotate_left (state.[ b ]) (mk_u32 12)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;impl_u32__rotate_left (state.[ d ]) (mk_u32 8)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;impl_u32__rotate_left (state.[ b ]) (mk_u32 7)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But &lt;code&gt;rotate_left&lt;/code&gt; is declared as &lt;code&gt;assume val impl_u32__rotate_left&#39;: x: u32 -&amp;gt; n: u32 -&amp;gt; u32&lt;/code&gt;&amp;mdash;no postcondition. Z3 cannot distinguish &lt;code&gt;rotate_left x 16&lt;/code&gt; from &lt;code&gt;rotate_left x 15&lt;/code&gt;. An implementation with amounts 15, 11, 7, 6&amp;mdash;each off by one, producing a weak cipher&amp;mdash;would &amp;ldquo;verify&amp;rdquo; identically.&lt;/p&gt;
&lt;p&gt;This is a &lt;em&gt;scope gap&lt;/em&gt;, not a facade gap: the model is faithful, but the verification cannot cover the core cryptographic property. E1 and E4 show models that are &lt;em&gt;wrong&lt;/em&gt;; E5 shows a model that is &lt;em&gt;right but incomplete&lt;/em&gt;. The axiomatization affects every primitive that uses rotations: SHA-256, ChaCha20, AES. HACL*&amp;rsquo;s &lt;code&gt;Lib.IntTypes&lt;/code&gt; demonstrates that defining rotations via shift-and-or is tractable&amp;mdash;the current library chose axiomatization for simplicity.&lt;/p&gt;
&lt;h2 id=&#34;classification-and-verification-results&#34;&gt;Classification and Verification Results&lt;/h2&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Gap&lt;/th&gt;
          &lt;th&gt;Class&lt;/th&gt;
          &lt;th&gt;Type&lt;/th&gt;
          &lt;th&gt;Exploits&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;While-loop fuel=0&lt;/td&gt;
          &lt;td&gt;I-A&lt;/td&gt;
          &lt;td&gt;Bug&lt;/td&gt;
          &lt;td&gt;E1, E4&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Reference erasure&lt;/td&gt;
          &lt;td&gt;I-B&lt;/td&gt;
          &lt;td&gt;Fundamental&lt;/td&gt;
          &lt;td&gt;&amp;mdash;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Debug/release divergence&lt;/td&gt;
          &lt;td&gt;I-C&lt;/td&gt;
          &lt;td&gt;Engineering&lt;/td&gt;
          &lt;td&gt;E2&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Opaque function trust&lt;/td&gt;
          &lt;td&gt;II-A&lt;/td&gt;
          &lt;td&gt;Feature&lt;/td&gt;
          &lt;td&gt;&amp;mdash;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Proof library axioms&lt;/td&gt;
          &lt;td&gt;II-B&lt;/td&gt;
          &lt;td&gt;Engineering&lt;/td&gt;
          &lt;td&gt;E5&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Unmodeled Drop/RAII&lt;/td&gt;
          &lt;td&gt;II-C&lt;/td&gt;
          &lt;td&gt;Fundamental&lt;/td&gt;
          &lt;td&gt;&amp;mdash;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;assume!&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;III-A&lt;/td&gt;
          &lt;td&gt;Feature&lt;/td&gt;
          &lt;td&gt;E3&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;We ran the F* typechecker (v2025.03.25) on all five extracted files. In full verification mode, three pass completely: E2, E3, and E5 all report &amp;ldquo;All verification conditions discharged successfully.&amp;rdquo; These are the strongest findings: F* &lt;em&gt;fully verifies&lt;/em&gt; the code, yet the security gaps are real and undetected. E1 and E4 fail full verification due to an F* solver limitation with inner &lt;code&gt;let rec&lt;/code&gt; definitions&amp;mdash;a failure unrelated to our exploits. In practice, projects using hax handle &lt;code&gt;while&lt;/code&gt;-loop modules via &lt;code&gt;ADMIT_MODULES&lt;/code&gt;, accepting them without full verification.&lt;/p&gt;
&lt;p&gt;The while-loop fuel=0 default is a one-line fix; we notified the hax maintainers prior to publication. The proof library axioms could be replaced with definitions. Reference erasure and unmodeled Drop are fundamental&amp;mdash;they would require extraction at the MIR level, as Aeneas does. Beyond these, the backend should offer a release-mode arithmetic option (&lt;code&gt;+.&lt;/code&gt; instead of &lt;code&gt;+!&lt;/code&gt;), and &lt;code&gt;assume!&lt;/code&gt; should check consistency against the proof context via a Z3 query before injecting axioms.&lt;/p&gt;
&lt;h2 id=&#34;the-full-picture&#34;&gt;The Full Picture&lt;/h2&gt;
&lt;p&gt;Our &lt;a href=&#34;https://eprint.iacr.org/2026/192&#34;&gt;previous work&lt;/a&gt; documented code &lt;em&gt;outside&lt;/em&gt; the verified perimeter: wrong specifications, false proofs, admitted modules. The verification facade concerns the perimeter itself. Even for code that is extracted and whose proofs are dispatched to Z3, the verified model can diverge from the deployed code&amp;mdash;because while loops are silently proof-inert, because release-mode arithmetic differs from the model, because &lt;code&gt;assume!&lt;/code&gt; can poison the proof context, and because axiomatized operations have no semantic content for Z3 to reason about.&lt;/p&gt;
&lt;p&gt;These gaps have direct FIPS implications. ML-KEM and ML-DSA implementations verified via hax are deployed where FIPS compliance is relevant. Rejection sampling in both algorithms uses &lt;code&gt;while&lt;/code&gt; loops. Production modules compile in release mode. The verification provides less assurance than it appears to.&lt;/p&gt;
&lt;p&gt;Together with our earlier findings, the full picture is now this. The &lt;a href=&#34;https://symbolic.software/blog/2026-02-12-cryspen-response/&#34;&gt;specifications are wrong&lt;/a&gt;. The &lt;a href=&#34;https://symbolic.software/blog/2026-02-12-cryspen-response/&#34;&gt;proofs are false or never checked&lt;/a&gt;. The &lt;a href=&#34;https://symbolic.software/blog/2026-02-12-cryspen-response/&#34;&gt;build system defaults to admitting everything&lt;/a&gt;. The &lt;a href=&#34;https://symbolic.software/blog/2026-02-05-cryspen/&#34;&gt;unverified wrappers&lt;/a&gt; contain implementation defects. The &lt;a href=&#34;https://symbolic.software/blog/2026-03-07-cryspen-tls/&#34;&gt;TLS implementations&lt;/a&gt; reject valid signatures and accept certificates from anyone. And now: even when verification &lt;em&gt;is&lt;/em&gt; performed, the model that F* verifies can diverge from the program that Rust executes.&lt;/p&gt;
&lt;p&gt;Hax is an active research project that makes genuine contributions to high-assurance cryptography. For pure, bounded-loop functions with precise specifications, it provides real verification. Our critique targets structural limitations of the extraction approach, not the project&amp;rsquo;s intent. But the translation pipeline is part of the trusted computing base, and verification claims for extracted code require the same qualification and defense-in-depth practices that the formal methods community has long advocated&amp;mdash;and that Cryspen has consistently failed to provide.&lt;/p&gt;





&lt;nav class=&#34;series-nav&#34; aria-label=&#34;Cryspen series (bottom)&#34;&gt;
  &lt;header class=&#34;series-nav-header&#34;&gt;
    &lt;span class=&#34;series-nav-label&#34;&gt;▶ Cryspen Series&lt;/span&gt;
    &lt;span class=&#34;series-nav-count&#34;&gt;5 posts · 2 papers · 1 talk&lt;/span&gt;
  &lt;/header&gt;
  &lt;ol class=&#34;series-nav-list&#34;&gt;
    
    
    &lt;li&gt;
      
      &lt;a href=&#34;https://symbolic.software/blog/2026-02-05-cryspen/&#34;&gt;On the Promises of &amp;#39;High-Assurance&amp;#39; Cryptography&lt;/a&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.02.05&lt;/span&gt;
    &lt;/li&gt;
    
    
    &lt;li&gt;
      
      &lt;a href=&#34;https://symbolic.software/blog/2026-02-12-cryspen-response/&#34;&gt;We Found Bugs in Cryspen&amp;#39;s Verified Code&lt;/a&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.02.12&lt;/span&gt;
    &lt;/li&gt;
    
    
    &lt;li&gt;
      
      &lt;a href=&#34;https://symbolic.software/blog/2026-02-17-cryspen-mldsa/&#34;&gt;Even More Bugs in Cryspen&amp;#39;s libcrux: ML-DSA&lt;/a&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.02.17&lt;/span&gt;
    &lt;/li&gt;
    
    
    &lt;li&gt;
      
      &lt;a href=&#34;https://symbolic.software/blog/2026-03-07-cryspen-tls/&#34;&gt;Cryspen&amp;#39;s Approach to TLS: A Critical Analysis&lt;/a&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.03.07&lt;/span&gt;
    &lt;/li&gt;
    
    
    &lt;li class=&#34;current&#34; aria-current=&#34;page&#34;&gt;
      
      &lt;span class=&#34;title&#34;&gt;The Verification Facade: Structural Gaps in Hax&lt;/span&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.04.07&lt;/span&gt;
    &lt;/li&gt;
    
  &lt;/ol&gt;
  &lt;div class=&#34;series-nav-footer&#34;&gt;
    &lt;div class=&#34;series-nav-row&#34;&gt;
      &lt;span class=&#34;series-nav-rowlabel&#34;&gt;Papers&lt;/span&gt;
      &lt;a href=&#34;https://eprint.iacr.org/2026/192&#34;&gt;&lt;em&gt;Verification Theatre&lt;/em&gt;&lt;/a&gt;
      &lt;span class=&#34;series-nav-sep&#34;&gt;·&lt;/span&gt;
      &lt;a href=&#34;https://eprint.iacr.org/2026/670&#34;&gt;&lt;em&gt;Verification Facade&lt;/em&gt;&lt;/a&gt;
    &lt;/div&gt;
    &lt;div class=&#34;series-nav-row&#34;&gt;
      &lt;span class=&#34;series-nav-rowlabel&#34;&gt;Talk&lt;/span&gt;
      &lt;a href=&#34;https://www.youtube.com/watch?v=TdOXza1-M_4&#34;&gt;&lt;em&gt;High Assurance Cryptography and the Ethics of Disclosure (OSTIF)&lt;/em&gt;&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/nav&gt;

</content:encoded>
      <category>Research</category>
      <category>Formal Verification</category>
      
    </item>
    
    <item>
      <title>Recommending Post-Quantum Native Design Under Epistemic Duress</title>
      <link>https://symbolic.software/blog/2026-04-02-pq-native/</link>
      <pubDate>Thu, 02 Apr 2026 00:00:00 &#43;0000</pubDate>
      <guid>https://symbolic.software/blog/2026-04-02-pq-native/</guid>
      <description>Symbolic Software is recommending post-quantum native design for all new cryptographic systems. This post examines the evidence behind that recommendation, its limitations, and the epistemic questions the industry should be confronting.</description>
      <content:encoded>&lt;p&gt;Effective immediately, Symbolic Software is recommending that all clients design new cryptographic systems as post-quantum native. This post explains the reasoning behind that recommendation, subjects the evidence to the scrutiny it deserves, and raises questions about the epistemic standards the applied cryptography community should be holding itself to as the post-quantum transition accelerates.&lt;/p&gt;
&lt;p&gt;The recommendation is, on balance, probably correct. But &amp;ldquo;probably correct&amp;rdquo; is not the standard this profession is supposed to work to. The gap between the two deserves honest examination.&lt;/p&gt;
&lt;h2 id=&#34;the-google-quantum-ai-result&#34;&gt;The Google Quantum AI Result&lt;/h2&gt;
&lt;p&gt;On March 30, 2026, a team led by Google Quantum AI&amp;mdash;with co-authors from UC Berkeley, the Ethereum Foundation, and Stanford&amp;mdash;&lt;a href=&#34;https://research.google/blog/safeguarding-cryptocurrency-by-disclosing-quantum-vulnerabilities-responsibly/&#34;&gt;published a whitepaper&lt;/a&gt; presenting updated quantum resource estimates for solving the elliptic curve discrete logarithm problem on the secp256k1 curve (ECDLP-256)&amp;mdash;the curve underpinning the vast majority of cryptocurrency wallet signatures and a significant share of TLS deployments. The headline claim is a nearly 20-fold reduction in physical qubit requirements compared to Litinski&amp;rsquo;s 2023 estimates, which were themselves already a substantial improvement over earlier work.&lt;/p&gt;
&lt;p&gt;Two compiled circuits are presented, representing different points on the space-time tradeoff curve:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Circuit A (low-qubit variant):&lt;/strong&gt; $\leq 1{,}200$ logical qubits and $\leq 90$ million Toffoli gates.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Circuit B (low-gate variant):&lt;/strong&gt; $\leq 1{,}450$ logical qubits and $\leq 70$ million Toffoli gates.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Under assumptions about superconducting qubit hardware (physical error rates at or below $10^{-3}$, planar degree-4 connectivity), these circuits are projected to execute on fewer than 500,000 physical qubits in a matter of minutes. The resource cost curve for ECDLP-256 is now tracking the same downward trajectory that RSA-2048 followed over the preceding decade.&lt;/p&gt;
&lt;p&gt;The result is significant regardless of one&amp;rsquo;s position on post-quantum timelines. It narrows the gap between theoretical quantum advantage and engineering feasibility in a way that demands serious attention.&lt;/p&gt;
&lt;h2 id=&#34;a-novel-disclosure-model&#34;&gt;A Novel Disclosure Model&lt;/h2&gt;
&lt;p&gt;The paper introduces something genuinely new to the quantum cryptanalysis literature: rather than publishing the full circuit constructions, the authors provide a zero-knowledge proof&amp;mdash;specifically, a Groth16 SNARK executed within the SP1 zkVM&amp;mdash;attesting that they possess a quantum kickmix circuit of the claimed size that correctly computes secp256k1 elliptic curve point addition on 9,024 Fiat-Shamir-sampled pseudo-random inputs. (A kickmix circuit, as defined in the paper, is composed of classical reversible logic gates, measurement-based uncomputation, and diagonal phasing gates for phase correction&amp;mdash;efficiently simulable classically, but structured for direct quantum execution.)&lt;/p&gt;
&lt;p&gt;The stated rationale is responsible disclosure. Publishing optimized quantum circuits for breaking deployed elliptic curve cryptography would hand a blueprint to any future adversary with access to a sufficiently large quantum computer. The ZK approach attempts to let the community verify the &lt;em&gt;plausibility&lt;/em&gt; of the resource estimate without providing the &lt;em&gt;means&lt;/em&gt; to execute the attack.&lt;/p&gt;
&lt;p&gt;This is an intellectually interesting approach and, in principle, a reasonable application of zero-knowledge techniques to a disclosure problem that has no good precedent. The authors deserve credit for thinking carefully about the responsible communication of quantum cryptanalytic capability, particularly in a domain&amp;mdash;cryptocurrency&amp;mdash;where public confidence is itself a security-relevant property.&lt;/p&gt;
&lt;h2 id=&#34;what-the-proof-coversand-what-it-doesnt&#34;&gt;What the Proof Covers&amp;mdash;and What It Doesn&amp;rsquo;t&lt;/h2&gt;
&lt;p&gt;The ZK proof must be evaluated precisely for what it attests to and what it leaves open.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What is proven:&lt;/strong&gt; The authors possess a quantum kickmix circuit that correctly computes secp256k1 point addition, and that circuit fits within the claimed resource counts. This is verified against 9,024 pseudo-random inputs derived via the Fiat-Shamir heuristic (SHAKE256 seeded with the circuit&amp;rsquo;s own bytes). The paper provides an explicit soundness bound: if the circuit produced incorrect outputs on more than 1% of inputs, the probability of passing all 9,024 tests is at most $(1-0.01)^{9024} \approx 2^{-130}$, yielding 128 bits of cryptographic security for the claim that the circuit is approximately correct on at least 99% of inputs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What is not proven:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The full Shor compilation is not attested.&lt;/strong&gt; The ZK proof covers the elliptic curve point addition subroutine, which the authors identify as the computational bottleneck. The reduction from point-addition cost to full end-to-end Shor execution cost relies on published windowed arithmetic techniques (double-and-add chains, interleaved modular multiplication). These techniques are well-established in the literature and the reduction is reasonable&amp;mdash;but it remains an inferential step. The proof does not verify the complete quantum circuit that would actually break a key. An error in the broader compilation&amp;mdash;the modular inversion, the quantum Fourier transform, the classical pre- and post-processing&amp;mdash;would not be caught by this proof.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The soundness guarantee covers approximate correctness, not exact correctness.&lt;/strong&gt; The paper&amp;rsquo;s soundness analysis establishes that the circuit is correct on at least 99% of inputs with $2^{-130}$ failure probability&amp;mdash;a strong bound. But Shor&amp;rsquo;s algorithm is tolerant of this: a superposition with 1% incorrect values causes the algorithm to fail at most 1% of the time. What is less explicitly analyzed is the end-to-end relationship between &amp;ldquo;the point addition subroutine is 99%-correct on random inputs&amp;rdquo; and &amp;ldquo;the full Shor circuit, composed of 28 windowed point additions interleaved with modular arithmetic and a quantum Fourier transform, produces correct discrete logarithms.&amp;rdquo; The subroutine correctness bound is rigorous; the compositional argument connecting it to the top-level ECDLP claim rests on standard but unstated assumptions about how errors in the subroutine propagate through the larger algorithm.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The physical qubit projection rests on hardware assumptions that do not yet hold.&lt;/strong&gt; The 500,000 physical qubit figure assumes error rates at or below $10^{-3}$ sustained across planar degree-4 connectivity at scale. Current superconducting qubit hardware has demonstrated error rates in this range on small systems, but maintaining these rates at the 500,000-qubit scale involves engineering challenges&amp;mdash;crosstalk, wiring density, cooling&amp;mdash;that have not been demonstrated. The projection is plausible given current trajectories, but it is a projection, not a measurement.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The circuits themselves are not available for independent analysis.&lt;/strong&gt; This is by design&amp;mdash;the entire point of the ZK approach is to withhold the circuits. But it means the community cannot independently verify the circuit architecture, check for optimization opportunities that might further reduce costs, or identify errors in the compilation strategy. The claim is auditable only to the extent that the ZK proof permits.&lt;/p&gt;
&lt;p&gt;None of these observations make the paper wrong. Taken together, they make the paper a strong signal that is not yet conclusive evidence. This distinction matters.&lt;/p&gt;
&lt;h2 id=&#34;the-risk-calculus-behind-the-recommendation&#34;&gt;The Risk Calculus Behind the Recommendation&lt;/h2&gt;
&lt;p&gt;Given the above, why is Symbolic Software recommending post-quantum native design?&lt;/p&gt;
&lt;p&gt;The answer is the asymmetry of consequences.&lt;/p&gt;
&lt;p&gt;Consider the two error modes. If the recommendation is premature&amp;mdash;if the quantum timeline turns out to be significantly longer than the resource estimates suggest&amp;mdash;the cost to clients is engineering overhead: larger key sizes, larger signatures, increased bandwidth, more complex protocol negotiations, and the implementation risks inherent in deploying newer cryptographic primitives. These costs are real and non-trivial, but they are recoverable. A system designed with ML-KEM and ML-DSA that turns out not to have needed them for another fifteen years is a system that over-invested in security. There are worse failure modes.&lt;/p&gt;
&lt;p&gt;If the recommendation is late&amp;mdash;if the estimates are roughly correct and classical elliptic curve cryptography becomes vulnerable within the operational lifetime of systems being designed today&amp;mdash;the consequences are categorically different. Private keys are extractable. Signatures are forgeable. Store-now-decrypt-later attacks, in which encrypted traffic captured today is decrypted by a future quantum adversary, become retroactively devastating. These consequences are not recoverable. Data that has been exfiltrated cannot be un-exfiltrated.&lt;/p&gt;
&lt;p&gt;This asymmetry is not new. It has been the standard argument for post-quantum preparedness for years. What the Google result changes is the &lt;em&gt;urgency&lt;/em&gt; of the timeline, not the &lt;em&gt;structure&lt;/em&gt; of the argument. The resource estimates have now improved to the point where &amp;ldquo;within the operational lifetime of systems being designed today&amp;rdquo; is no longer a worst-case assumption but a median-case projection.&lt;/p&gt;
&lt;h2 id=&#34;the-harder-question-epistemic-standards-under-pressure&#34;&gt;The Harder Question: Epistemic Standards Under Pressure&lt;/h2&gt;
&lt;p&gt;The risk calculus is straightforward. The harder question is about the epistemic standards the community is applying as it navigates the post-quantum transition.&lt;/p&gt;
&lt;p&gt;Applied cryptography has historically operated under a norm of open, reproducible, and fully auditable evidence. Protocols are published. Proofs are checked. Implementations are reviewed. When a vulnerability is claimed, the expectation is that the claim can be independently verified&amp;mdash;not as a matter of trust, but as a matter of scientific practice. This norm exists for good reason: the history of the field is littered with examples of informal reasoning, indirect signals, and trusted-but-wrong claims leading to catastrophic outcomes. Snake oil gets sold on the basis of plausible-sounding arguments. Bad standards get adopted because the evidence supporting them was not subjected to sufficient scrutiny.&lt;/p&gt;
&lt;p&gt;The Google team&amp;rsquo;s ZK disclosure model represents a deliberate departure from this norm. The departure is well-motivated&amp;mdash;there is a legitimate argument that publishing optimized quantum attack circuits would cause more harm than benefit. But it creates a new epistemic regime in which the community is asked to make critical infrastructure decisions based on claims that are, by design, not fully auditable.&lt;/p&gt;
&lt;p&gt;Several aspects of this regime deserve scrutiny:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The precedent effect.&lt;/strong&gt; If &amp;ldquo;trust the ZK proof, don&amp;rsquo;t ask to see the circuits&amp;rdquo; becomes the accepted standard for quantum cryptanalysis disclosure, the community will be building security policy on a foundation where the underlying evidence is structurally unverifiable beyond what the prover chooses to reveal. This is an uncomfortable position regardless of the credibility of the prover&amp;mdash;and Google Quantum AI is a very credible prover. The question is not whether &lt;em&gt;this team&lt;/em&gt; can be trusted, but whether &lt;em&gt;this disclosure model&lt;/em&gt; should become normative. Norms are not evaluated against the best-case actor; they are evaluated against the full range of actors who will invoke them.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The gap between verified and verifiable.&lt;/strong&gt; A ZK proof provides cryptographic assurance that a specific computation was performed correctly. It does not provide the community with the ability to &lt;em&gt;understand&lt;/em&gt; what was computed, to identify potential improvements, or to catch errors in the broader reasoning that connects the proven subroutine to the top-level claim. In traditional disclosure, a published circuit can be independently optimized, refuted, or extended. Under ZK disclosure, the community&amp;rsquo;s role is reduced from &lt;em&gt;auditor&lt;/em&gt; to &lt;em&gt;verifier of a predefined claim&lt;/em&gt;. These are different epistemic positions, and the latter is weaker.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The relationship between conservatism and rigor.&lt;/strong&gt; The natural response to all of the above is: &amp;ldquo;Just be conservative. Assume the attack works and design against it. That&amp;rsquo;s Kerckhoffs&amp;rsquo; principle applied to threat modeling.&amp;rdquo; This response is correct as far as it goes, and it is the basis of the recommendation being made here. But there is a meaningful difference between conservatism as a &lt;em&gt;design principle&lt;/em&gt;&amp;mdash;where uncertainty is resolved in favor of caution&amp;mdash;and conservatism as an &lt;em&gt;epistemic shortcut&lt;/em&gt;&amp;mdash;where uncertainty is invoked to avoid the hard work of evaluating evidence rigorously. The former strengthens the field. The latter, if left unchecked, erodes the standards that make the field&amp;rsquo;s work meaningful.&lt;/p&gt;
&lt;p&gt;The applied cryptography community has spent decades building a culture in which claims are expected to be substantiated, assumptions are expected to be explicit, and &amp;ldquo;it seems plausible&amp;rdquo; is not considered sufficient grounds for action. The post-quantum transition is now testing whether that culture holds under the pressure of urgency.&lt;/p&gt;
&lt;h2 id=&#34;what-should-happen-next&#34;&gt;What Should Happen Next&lt;/h2&gt;
&lt;p&gt;Several concrete steps would strengthen the evidentiary basis for post-quantum migration decisions:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Independent reproduction of the resource estimates.&lt;/strong&gt; The Google result is currently a single data point. A well-attested single data point, but a single data point nonetheless. Independent teams should attempt to reproduce or improve on the ECDLP-256 resource estimates using the publicly available literature on quantum arithmetic and Shor compilation. This is not an expression of doubt in the Google result&amp;mdash;it is standard scientific practice. A global cryptographic migration should not be calibrated against a claim that only one team can verify.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;End-to-end soundness analysis of the ZK disclosure model.&lt;/strong&gt; The paper provides a clear soundness bound for the subroutine ($2^{-130}$ failure probability for 99%-correctness). What remains implicit is the compositional argument: how subroutine-level approximate correctness propagates through the full Shor circuit (28 windowed point additions, modular arithmetic, QFT) to yield a correct discrete logarithm. The authors also note a striking irony&amp;mdash;the Groth16 SNARK itself relies on pairing-friendly elliptic curves vulnerable to the very quantum attacks the paper analyzes&amp;mdash;which bounds the proof&amp;rsquo;s validity to the pre-CRQC era. A formal end-to-end analysis connecting the ZK-attested subroutine properties to the top-level ECDLP claim would strengthen the disclosure model considerably.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Continued hardware benchmarking against the projected requirements.&lt;/strong&gt; The 500,000 physical qubit projection is credible given current trajectories, but it depends on engineering parameters that should be tracked publicly. As superconducting qubit systems scale, the community should maintain a clear, openly accessible mapping between demonstrated hardware capabilities and the requirements of the published quantum circuits. This mapping would provide a more rigorous basis for timeline estimates than trend extrapolation alone.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;An honest conversation about epistemic norms under uncertainty.&lt;/strong&gt; The applied cryptography community needs to explicitly discuss how it will handle situations where the evidence for a threat is strong but not fully transparent. The current post-quantum transition is the first instance of this problem at scale, but it will not be the last. Establishing clear norms now&amp;mdash;about what constitutes sufficient evidence for different classes of decisions, about the obligations of provers who withhold details, and about the role of independent verification&amp;mdash;will serve the field well beyond the current transition.&lt;/p&gt;
&lt;h2 id=&#34;the-recommendation&#34;&gt;The Recommendation&lt;/h2&gt;
&lt;p&gt;Symbolic Software is advising all clients to design new cryptographic systems as post-quantum native. This means ML-KEM for key encapsulation, ML-DSA for digital signatures, and hybrid constructions where backward compatibility with classical systems is required during transition. The recommendation applies to systems currently in the design phase; guidance on migrating existing systems is available on a per-engagement basis.&lt;/p&gt;
&lt;p&gt;This recommendation is made with full awareness that the evidence base, while strong, is not conclusive in the way the profession traditionally demands. The risk asymmetry justifies action. The evidentiary gaps justify continued scrutiny. Both of these things can be true simultaneously.&lt;/p&gt;
&lt;p&gt;The post-quantum transition should be pursued with urgency. It should not be pursued with credulity. The applied cryptography community&amp;rsquo;s greatest asset is its insistence on rigor, and that insistence should not be the first casualty of the transition it is meant to protect.&lt;/p&gt;
&lt;p&gt;For the operational follow-through on this recommendation — primitive selection, hybrid construction, TLS migration mechanics, library landscape, conformance testing, rollout strategy, and the bug classes we keep finding in audits — see our free &lt;a href=&#34;https://pq-migration.symbolic.software/static/pdf/playbook.pdf&#34;&gt;Post-Quantum Migration Playbook&lt;/a&gt;, companion to the live &lt;a href=&#34;https://pq-migration.symbolic.software&#34;&gt;PQ Migration Readiness scorecard and scanner&lt;/a&gt;.&lt;/p&gt;
</content:encoded>
      <category>Research</category>
      <category>Post-Quantum</category>
      
    </item>
    
    <item>
      <title>Applied Cryptography: Free Online Course for 50 Lebanese University Students This Summer</title>
      <link>https://symbolic.software/blog/2026-03-23-applied-cryptography-summer/</link>
      <pubDate>Mon, 23 Mar 2026 00:00:00 &#43;0000</pubDate>
      <guid>https://symbolic.software/blog/2026-03-23-applied-cryptography-summer/</guid>
      <description>We&#39;re opening 50 spots for students at Lebanese universities to take the Applied Cryptography course online, completely free of charge, starting June 2026. Applications are open now.</description>
      <content:encoded>&lt;p&gt;The &lt;a href=&#34;https://appliedcryptography.page&#34;&gt;Applied Cryptography&lt;/a&gt; course that I taught last year at the American University of Beirut is coming back this summer as a free online program, open to 50 students at Lebanese universities nationwide.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This is a full-featured course with strong instructor-student engagement&lt;/strong&gt;. The same lectures, the same problem sets, the same projects, the same rigor, delivered online over the summer to a small, selectively admitted cohort. If you are a university student in Lebanon and you want to learn modern cryptography properly, this is your application: &lt;a href=&#34;https://register.appliedcryptography.page/&#34;&gt;&lt;strong&gt;apply here&lt;/strong&gt;&lt;/a&gt;. The deadline is &lt;strong&gt;May 10, 2026&lt;/strong&gt;. The course starts &lt;strong&gt;June 9, 2026&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id=&#34;what-the-course-covers&#34;&gt;What the Course Covers&lt;/h2&gt;
&lt;p&gt;Applied Cryptography is a two-part course. The first part builds the theoretical foundations: provable security, pseudorandomness, chosen-plaintext and chosen-ciphertext attacks, collision-resistant hash functions, the discrete logarithm problem, Diffie-Hellman, elliptic curves, and digital signatures. The second part applies those foundations to real-world systems: TLS, secure messaging (Signal, OTR, MLS), end-to-end encrypted cloud storage, post-quantum cryptography, zero-knowledge proofs, cryptocurrency cryptography, high-assurance implementations, and secure multiparty computation.&lt;/p&gt;
&lt;p&gt;The course has 16 topics, 8 problem sets, and 8 hands-on projects (students choose one or two). Projects range from building a password manager to designing a secure messenger, formally verifying a TLS-like protocol in ProVerif, implementing a zero-knowledge battleship game, and migrating a system to post-quantum cryptography. The programming languages are Go and Rust.&lt;/p&gt;
&lt;p&gt;The required textbooks are Mike Rosulek&amp;rsquo;s &lt;em&gt;The Joy of Cryptography&lt;/em&gt; and Jean-Philippe Aumasson&amp;rsquo;s &lt;em&gt;Serious Cryptography, 2nd Edition&lt;/em&gt;. There is also an extensive reading list of around 90 research papers and technical resources, all linked from the &lt;a href=&#34;https://appliedcryptography.page&#34;&gt;course website&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;how-it-works&#34;&gt;How It Works&lt;/h2&gt;
&lt;p&gt;The program runs entirely online:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Lectures&lt;/strong&gt; are both delivered live and &lt;strong&gt;also&lt;/strong&gt; pre-recorded and published on YouTube. Watch them on your own schedule.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Discussion, Q&amp;amp;A, and community&lt;/strong&gt; happen on a dedicated &lt;strong&gt;Discord server&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Problem sets and projects&lt;/strong&gt; are submitted and graded through &lt;strong&gt;Google Classroom&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All course materials&amp;mdash;slides, readings, problem sets, project specifications&amp;mdash;are already publicly available on the &lt;a href=&#34;https://appliedcryptography.page&#34;&gt;course website&lt;/a&gt; under a Creative Commons license. The online program adds structured pacing, graded assignments, direct access to the instructor, and a cohort of peers to work with.&lt;/p&gt;
&lt;h2 id=&#34;who-should-apply&#34;&gt;Who Should Apply&lt;/h2&gt;
&lt;p&gt;You must be currently enrolled at a qualifying Lebanese university. Undergraduate or graduate. We will select 50 students based on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Motivation.&lt;/strong&gt; Why do you want to learn cryptography? What do you intend to do with it?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Background.&lt;/strong&gt; Familiarity with at least one programming language and basic mathematical maturity (discrete math, probability, or any proof-based coursework).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Diversity.&lt;/strong&gt; We want representation from across Lebanese universities and academic backgrounds, not just the usual suspects.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;No prior cryptography experience is required. The course starts from first principles. If you have never seen a security reduction before, that is fine&amp;mdash;you will by the end.&lt;/p&gt;
&lt;h2 id=&#34;certificate-and-recommendation-letters&#34;&gt;Certificate and Recommendation Letters&lt;/h2&gt;
&lt;p&gt;Students who complete the course will receive a signed certificate of completion. High-achieving students may be eligible for personalized letters of recommendation for graduate school applications.&lt;/p&gt;
&lt;h2 id=&#34;why-were-doing-this&#34;&gt;Why We&amp;rsquo;re Doing This&lt;/h2&gt;
&lt;p&gt;Lebanon has a remarkable density of talented computer science students who, for various well-known reasons, do not always have access to specialized coursework in areas like cryptography. This is not a commentary on any university&amp;rsquo;s curriculum. It is an observation that the gap between what is taught in a standard undergraduate CS program and what is needed to do serious work in cryptography&amp;mdash;whether in research, industry, or open-source&amp;mdash;is large, and that closing it should not depend on which university you happened to enroll in or whether you can afford a semester abroad.&lt;/p&gt;
&lt;p&gt;This course exists because I believe applied cryptography education should be accessible to any motivated student in Lebanon who wants it. The course materials are already open. The lectures will be open. The only scarce resource is structured mentorship and graded feedback, and we are making that available to 50 students this summer at no cost.&lt;/p&gt;
&lt;h2 id=&#34;looking-for-tas&#34;&gt;Looking for TAs&lt;/h2&gt;
&lt;p&gt;My students from last semester &amp;ndash; in case you&amp;rsquo;re reading this, get in touch. There&amp;rsquo;s no way I&amp;rsquo;m grading fifty students on my lonesome again! I need your help!&lt;/p&gt;
&lt;h2 id=&#34;apply&#34;&gt;Apply&lt;/h2&gt;
&lt;p&gt;The application is short. Tell us who you are, where you study, why you want to take this course, and what background you bring. That&amp;rsquo;s it.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://register.appliedcryptography.page/&#34;&gt;&lt;strong&gt;Apply now!&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
</content:encoded>
      <category>Announcement</category>
      
    </item>
    
    <item>
      <title>Crucible: Forging Post-Quantum Implementations in the Fire of Real Audit Findings</title>
      <link>https://symbolic.software/blog/2026-03-23-crucible/</link>
      <pubDate>Mon, 23 Mar 2026 00:00:00 &#43;0000</pubDate>
      <guid>https://symbolic.software/blog/2026-03-23-crucible/</guid>
      <description>We tested 15 ML-KEM and ML-DSA implementations across 5 languages. Here&#39;s what we found — and what we didn&#39;t.</description>
      <content:encoded>&lt;p&gt;NIST&amp;rsquo;s Known Answer Tests verify final outputs. Formal verification tools verify abstract models. Neither catches the bugs that live in between — the off-by-one rounding, the bounds check compiled to dead code, the inverse NTT quietly omitted from the decryption path.&lt;/p&gt;
&lt;p&gt;We know these bugs exist because we&amp;rsquo;ve &lt;a href=&#34;https://symbolic.software/blog/2026-02-05-cryspen/&#34;&gt;found them&lt;/a&gt; in production cryptographic code, including code marketed as &amp;ldquo;formally verified.&amp;rdquo; So we built a tool to catch them systematically.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/symbolicsoft/crucible&#34;&gt;Crucible&lt;/a&gt; is a conformance testing framework for ML-KEM (FIPS 203) and ML-DSA (FIPS 204) implementations. It encodes the specific bug classes we&amp;rsquo;ve encountered in real audits as targeted, reusable test batteries that any implementation can be wired up to. Then we pointed it at every ML-KEM and ML-DSA implementation we could find.&lt;/p&gt;
&lt;h2 id=&#34;the-setup&#34;&gt;The Setup&lt;/h2&gt;
&lt;p&gt;Crucible communicates with implementations through a simple JSON line protocol over stdin/stdout. A thin harness shim maps Crucible&amp;rsquo;s function calls to the implementation&amp;rsquo;s API. We built harnesses for 15 implementations across Rust, Go, C, C++20, and Java:&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Implementation&lt;/th&gt;
          &lt;th&gt;Language&lt;/th&gt;
          &lt;th&gt;Who uses it&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/cryspen/libcrux&#34;&gt;libcrux&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;Rust&lt;/td&gt;
          &lt;td&gt;Cryspen&amp;rsquo;s formally verified library&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/symbolicsoft/kyber-k2so&#34;&gt;Kyber-K2SO&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;Go&lt;/td&gt;
          &lt;td&gt;Our own implementation&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/pq-code-package/mlkem-native&#34;&gt;mlkem-native&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;C&lt;/td&gt;
          &lt;td&gt;Ships inside AWS-LC and liboqs&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://pkg.go.dev/crypto/mlkem&#34;&gt;Go stdlib&lt;/a&gt; &lt;code&gt;crypto/mlkem&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;Go&lt;/td&gt;
          &lt;td&gt;Every Go 1.26+ binary using TLS 1.3&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/aws/aws-lc&#34;&gt;AWS-LC&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;C&lt;/td&gt;
          &lt;td&gt;AWS KMS, S3, CloudFront&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/pq-crystals/kyber&#34;&gt;pq-crystals ref&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;C&lt;/td&gt;
          &lt;td&gt;The original reference from the algorithm designers&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/cloudflare/circl&#34;&gt;CIRCL&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;Go&lt;/td&gt;
          &lt;td&gt;Cloudflare&amp;rsquo;s TLS deployment&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/open-quantum-safe/liboqs&#34;&gt;liboqs&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;C&lt;/td&gt;
          &lt;td&gt;The most widely used PQC prototyping library&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/bcgit/bc-java&#34;&gt;Bouncy Castle&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;Java&lt;/td&gt;
          &lt;td&gt;The dominant JVM crypto library&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/wolfSSL/wolfssl&#34;&gt;wolfCrypt&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;C&lt;/td&gt;
          &lt;td&gt;Embedded/IoT, FIPS 140-3 validated&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/itzmeanjan/ml-kem&#34;&gt;itzmeanjan/ml-kem&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;C++20&lt;/td&gt;
          &lt;td&gt;Header-only, fully &lt;code&gt;constexpr&lt;/code&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/rustpq/pqcrypto&#34;&gt;pqcrypto&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;Rust&lt;/td&gt;
          &lt;td&gt;Rust FFI bindings to PQClean&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/PQClean/PQClean&#34;&gt;PQClean&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;C&lt;/td&gt;
          &lt;td&gt;Clean portable C implementations&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/trailofbits/ml-dsa&#34;&gt;Trail of Bits ml-dsa&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;Go&lt;/td&gt;
          &lt;td&gt;Side-channel resistant ML-DSA&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The ML-KEM battery has 78 tests across 6 categories (compression arithmetic, NTT correctness, coefficient bounds enforcement, decapsulation robustness, serialization, rejection sampling). The ML-DSA battery has 51 tests across 6 categories (norm checks, arithmetic, signing internals, verification edge cases, serialization, constant-time behavior). Every test is tagged with the bug class it targets, the FIPS spec section it verifies, and — where applicable — the real-world audit finding that motivated it.&lt;/p&gt;
&lt;h2 id=&#34;the-results&#34;&gt;The Results&lt;/h2&gt;
&lt;p&gt;We ran 1,296 test executions. Here&amp;rsquo;s the ML-KEM scorecard:&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Implementation&lt;/th&gt;
          &lt;th&gt;Pass&lt;/th&gt;
          &lt;th&gt;Fail&lt;/th&gt;
          &lt;th&gt;Skip&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;Reference (Crucible)&lt;/td&gt;
          &lt;td&gt;78&lt;/td&gt;
          &lt;td&gt;0&lt;/td&gt;
          &lt;td&gt;0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;libcrux v0.0.8&lt;/td&gt;
          &lt;td&gt;54&lt;/td&gt;
          &lt;td&gt;0&lt;/td&gt;
          &lt;td&gt;24&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Kyber-K2SO v1.1.0&lt;/td&gt;
          &lt;td&gt;54&lt;/td&gt;
          &lt;td&gt;0&lt;/td&gt;
          &lt;td&gt;24&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;mlkem-native&lt;/td&gt;
          &lt;td&gt;21&lt;/td&gt;
          &lt;td&gt;0&lt;/td&gt;
          &lt;td&gt;45&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Go stdlib&lt;/td&gt;
          &lt;td&gt;25&lt;/td&gt;
          &lt;td&gt;0&lt;/td&gt;
          &lt;td&gt;45&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;AWS-LC&lt;/td&gt;
          &lt;td&gt;33&lt;/td&gt;
          &lt;td&gt;0&lt;/td&gt;
          &lt;td&gt;45&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;CIRCL (Cloudflare)&lt;/td&gt;
          &lt;td&gt;33&lt;/td&gt;
          &lt;td&gt;0&lt;/td&gt;
          &lt;td&gt;45&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;liboqs&lt;/td&gt;
          &lt;td&gt;33&lt;/td&gt;
          &lt;td&gt;0&lt;/td&gt;
          &lt;td&gt;45&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;wolfCrypt&lt;/td&gt;
          &lt;td&gt;33&lt;/td&gt;
          &lt;td&gt;0&lt;/td&gt;
          &lt;td&gt;45&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;itzmeanjan&lt;/td&gt;
          &lt;td&gt;21&lt;/td&gt;
          &lt;td&gt;0&lt;/td&gt;
          &lt;td&gt;45&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Bouncy Castle 1.80&lt;/td&gt;
          &lt;td&gt;25&lt;/td&gt;
          &lt;td&gt;2&lt;/td&gt;
          &lt;td&gt;45&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;pq-crystals ref&lt;/td&gt;
          &lt;td&gt;19&lt;/td&gt;
          &lt;td&gt;2&lt;/td&gt;
          &lt;td&gt;45&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;PQClean&lt;/td&gt;
          &lt;td&gt;19&lt;/td&gt;
          &lt;td&gt;2&lt;/td&gt;
          &lt;td&gt;45&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;pqcrypto (rustpq)&lt;/td&gt;
          &lt;td&gt;21&lt;/td&gt;
          &lt;td&gt;6&lt;/td&gt;
          &lt;td&gt;45&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;(Skipped tests are NTT/sampling internals that implementations don&amp;rsquo;t expose through their public API. Errors from single-parameter-set harnesses are omitted for clarity.)&lt;/p&gt;
&lt;h3 id=&#34;finding-1-missing-encapsulation-key-modulus-check&#34;&gt;Finding 1: Missing Encapsulation Key Modulus Check&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Affected:&lt;/strong&gt; pq-crystals/kyber ref, PQClean, pqcrypto/rustpq
&lt;strong&gt;Severity:&lt;/strong&gt; Conformance gap
&lt;strong&gt;Spec reference:&lt;/strong&gt; FIPS 203 §7.2, Equation 7.1&lt;/p&gt;
&lt;p&gt;FIPS 203 requires that before encapsulation, the encapsulation key must pass a modulus check:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;test ← ByteEncode₁₂(ByteDecode₁₂(ek[0 : 384k])). If test ≠ ek[0 : 384k], then input checking failed.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;This check ensures that no 12-bit coefficient in the serialized public key encodes a value ≥ q = 3329. All three affected implementations accept a key with coefficient value 3329 without error. None of them provide a separate key validation function.&lt;/p&gt;
&lt;p&gt;This is a spec conformance gap, not a vulnerability. The practical impact is limited to interoperability: if two implementations handle an out-of-range coefficient differently (one reduces mod q, another wraps), the same key produces different shared secrets. The pq-crystals codebase predates FIPS 203 — the modulus check was added during standardization.&lt;/p&gt;
&lt;h3 id=&#34;finding-2-bouncy-castle-accepts-wrong-length-decapsulation-keys&#34;&gt;Finding 2: Bouncy Castle Accepts Wrong-Length Decapsulation Keys&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Affected:&lt;/strong&gt; Bouncy Castle 1.80 (Java)
&lt;strong&gt;Severity:&lt;/strong&gt; Input validation gap
&lt;strong&gt;Spec reference:&lt;/strong&gt; FIPS 203 §7.3&lt;/p&gt;
&lt;p&gt;Bouncy Castle&amp;rsquo;s &lt;code&gt;MLKEMPrivateKeyParameters&lt;/code&gt; constructor accepts byte arrays of any length without validating against the expected size for the parameter set. Crucible sends a decapsulation key that is 1 byte too short (2399 bytes for ML-KEM-768, expected 2400) and Bouncy Castle proceeds with decapsulation.&lt;/p&gt;
&lt;p&gt;A truncated dk means the implicit rejection secret &lt;code&gt;z&lt;/code&gt; is partially missing, which could weaken the FO transform&amp;rsquo;s security guarantee. In practice, the dk is private and an attacker wouldn&amp;rsquo;t normally control it — but a corrupted key file should fail loudly rather than silently produce wrong results.&lt;/p&gt;
&lt;h3 id=&#34;what-about-the-other-11-implementations&#34;&gt;What About the Other 11 Implementations?&lt;/h3&gt;
&lt;p&gt;They all passed. Every test. libcrux, mlkem-native, AWS-LC, CIRCL, liboqs, wolfCrypt, itzmeanjan, the Go standard library, and Trail of Bits ml-dsa — all clean.&lt;/p&gt;
&lt;h2 id=&#34;what-this-means&#34;&gt;What This Means&lt;/h2&gt;
&lt;p&gt;We set out to find the kinds of bugs we&amp;rsquo;d found in audits: missing inverse NTTs, dead bounds checks, incorrect rounding, timing leaks. We built 129 tests specifically targeting those bug classes. We tested the 15 most deployed post-quantum implementations in the world.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We found two conformance gaps and zero security vulnerabilities.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is actually the good news. It means the implementations that matter — the ones shipping inside AWS, Cloudflare, the Go standard library, and wolfSSL&amp;rsquo;s FIPS module — are doing the right things. The FIPS 203 standardization process added the right checks. The formally verified implementations (libcrux) pass. The clean-room implementations (mlkem-native, CIRCL, Trail of Bits) pass.&lt;/p&gt;
&lt;p&gt;The bugs we designed Crucible to catch existed in the pre-FIPS era and have largely been fixed. The ecosystem learned from the Cryspen findings, from KyberSlash, from the various timing advisories. The 2026 implementations are better than the 2024 ones.&lt;/p&gt;
&lt;h2 id=&#34;crucible-is-open-source&#34;&gt;Crucible Is Open Source&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/symbolicsoft/crucible&#34;&gt;Crucible is available on GitHub.&lt;/a&gt; It ships with harness templates for Rust, Go, and C. If you maintain an ML-KEM or ML-DSA implementation, wire it up and run the battery. If you find a bug we missed, it becomes a new test. The framework grows sharper over time.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;cargo build --release
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;cargo run --bin crucible -- ./your-harness
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;cargo run --bin crucible -- ./your-harness --battery ml-dsa --json
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The test batteries will continue to expand as we encounter new bug classes in future audits. The naming isn&amp;rsquo;t coincidental — like the &lt;a href=&#34;https://www.starwars.com/databank/crucible&#34;&gt;ancient Jedi vessel&lt;/a&gt; where younglings forge their lightsabers, Crucible is where implementations prove they&amp;rsquo;re truly sound, or reveal where they&amp;rsquo;re flawed.&lt;/p&gt;
&lt;p&gt;For more on where Crucible fits in a production post-quantum migration — primitive selection, hybrid constructions, library evaluation, rollout strategy — see our &lt;a href=&#34;https://pq-migration.symbolic.software/static/pdf/playbook.pdf&#34;&gt;Post-Quantum Migration Playbook&lt;/a&gt; and the companion &lt;a href=&#34;https://pq-migration.symbolic.software&#34;&gt;PQ Migration Readiness scorecard and scanner&lt;/a&gt;.&lt;/p&gt;
</content:encoded>
      <category>Software</category>
      <category>Research</category>
      <category>Crucible</category>
      <category>Kyber-K2SO</category>
      <category>Post-Quantum</category>
      
    </item>
    
    <item>
      <title>Summer 2026 Research Internship at Symbolic Software</title>
      <link>https://symbolic.software/blog/2026-03-19-internship/</link>
      <pubDate>Thu, 19 Mar 2026 00:00:00 &#43;0000</pubDate>
      <guid>https://symbolic.software/blog/2026-03-19-internship/</guid>
      <description>We&#39;re looking for a research intern to join us this summer and contribute to new papers on real-world cryptographic constructions.</description>
      <content:encoded>&lt;p&gt;Symbolic Software is looking for a research intern to join us for the summer of 2026. This is a hands-on position: you will co-author papers with me, &lt;a href=&#34;https://nadim.computer&#34;&gt;Nadim Kobeissi&lt;/a&gt;, on real-world cryptography, with a focus on designing new secure constructions that address open problems in today&amp;rsquo;s state-of-the-art cryptographic applications.&lt;/p&gt;
&lt;p&gt;I already have specific, concrete research projects and I want you to help me turn them into solid papers and artifacts that we can publish at strong conferences.&lt;/p&gt;
&lt;h2 id=&#34;what-were-working-on&#34;&gt;What We&amp;rsquo;re Working On&lt;/h2&gt;
&lt;p&gt;The research agenda centers on the gap between cryptographic theory and deployed systems. Cryptographic applications today&amp;mdash;secure messaging, authenticated key exchange, post-quantum migration, end-to-end encrypted media&amp;mdash;rely on constructions that were often designed under assumptions that no longer hold, or that leave meaningful security properties on the table. The world is constantly revolving, and from this chaos, chaos, we want to re-resolve the facts into a new understanding of the state of the art.&lt;/p&gt;
&lt;p&gt;Concretely, the internship will involve working on new papers that propose, formalize, and analyze cryptographic constructions addressing real problems in real systems. This means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Identifying concrete shortcomings&lt;/strong&gt; in cryptographic constructions used by deployed applications today.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Designing new constructions&lt;/strong&gt; that improve on the state of the art&amp;mdash;whether that means stronger security guarantees, better efficiency, or cleaner compositional properties.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Writing security proofs&lt;/strong&gt; and building formal or semi-formal arguments for why the new constructions achieve their claimed properties.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Producing publication-ready papers&lt;/strong&gt; targeting top-tier venues in applied cryptography and security.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The specific problems we tackle will depend in part on your interests and strengths, but the common thread is practical relevance: we are not interested in constructions that exist only to be proven secure. We want constructions that people will actually deploy.&lt;/p&gt;
&lt;h2 id=&#34;who-should-apply&#34;&gt;Who Should Apply&lt;/h2&gt;
&lt;p&gt;You should be a graduate student (Master&amp;rsquo;s or PhD) in cryptography, computer science, or a related field&amp;mdash;or an exceptional undergraduate with demonstrated research ability. What matters most:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Strong foundations in cryptography.&lt;/strong&gt; You should be comfortable with standard security definitions, reduction-based proofs, and the landscape of modern cryptographic primitives. If you&amp;rsquo;ve taken a graduate-level cryptography course and found it straightforward, that&amp;rsquo;s a good sign.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Interest in real-world systems.&lt;/strong&gt; The best candidates will have genuine curiosity about how cryptography is actually used&amp;mdash;and misused&amp;mdash;in practice. If you&amp;rsquo;ve ever read a protocol specification and spotted something that bothered you, we should talk.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Writing ability.&lt;/strong&gt; Research papers require clear, precise writing. You don&amp;rsquo;t need to have published before, but you should be able to communicate technical ideas effectively in English.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Self-direction.&lt;/strong&gt; This is a small team. You&amp;rsquo;ll have guidance and close collaboration, but you should be capable of reading papers independently, formulating questions, and pursuing ideas without constant oversight.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Intellectual honesty.&lt;/strong&gt; Most especially, I&amp;rsquo;m looking for candidates that are honest about their work, how it&amp;rsquo;s conducted, and who are able to examine both third-party systems and their own research output critically.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Prior publications are a plus but not required. What matters is that you can think clearly about cryptographic problems and are motivated to produce work that has real impact.&lt;/p&gt;
&lt;h2 id=&#34;details&#34;&gt;Details&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Duration:&lt;/strong&gt; Summer 2026 (approximately 3 months, with flexibility on exact dates).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Location:&lt;/strong&gt; Remote. Candidates within anywhere that is around the Paris, France time zone are preferred.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Compensation:&lt;/strong&gt; Paid internship. Details discussed during the application process.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deadline:&lt;/strong&gt; April 20, 2026&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;how-to-apply&#34;&gt;How to Apply&lt;/h2&gt;
&lt;p&gt;Send an email to &lt;a href=&#34;mailto:nadim@symbolic.software&#34;&gt;nadim@symbolic.software&lt;/a&gt; with:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Your CV or resume.&lt;/li&gt;
&lt;li&gt;A brief note (a few paragraphs is fine) on why this internship interests you and what kinds of cryptographic problems you find compelling.&lt;/li&gt;
&lt;li&gt;Any relevant prior work&amp;mdash;publications, thesis drafts, course projects, or open-source contributions.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;No formal cover letter needed. Just be direct about who you are and what you want to work on. I already have specific, concrete research projects and I want you to help me turn them into solid papers and artifacts that we can publish at strong conferences.&lt;/p&gt;
</content:encoded>
      <category>Announcement</category>
      
    </item>
    
    <item>
      <title>Cryspen&#39;s Approach to TLS: A Critical Analysis</title>
      <link>https://symbolic.software/blog/2026-03-07-cryspen-tls/</link>
      <pubDate>Sat, 07 Mar 2026 00:00:00 &#43;0000</pubDate>
      <guid>https://symbolic.software/blog/2026-03-07-cryspen-tls/</guid>
      <description>An examination of Cryspen&#39;s TLS implementations reveals 75% of valid ECDSA signatures rejected, authentication tags silently dropped, no certificate validation, and remote denial-of-service vectors.</description>
      <content:encoded>




&lt;nav class=&#34;series-nav&#34; aria-label=&#34;Cryspen series (top)&#34;&gt;
  &lt;header class=&#34;series-nav-header&#34;&gt;
    &lt;span class=&#34;series-nav-label&#34;&gt;▶ Cryspen Series&lt;/span&gt;
    &lt;span class=&#34;series-nav-count&#34;&gt;5 posts · 2 papers · 1 talk&lt;/span&gt;
  &lt;/header&gt;
  &lt;ol class=&#34;series-nav-list&#34;&gt;
    
    
    &lt;li&gt;
      
      &lt;a href=&#34;https://symbolic.software/blog/2026-02-05-cryspen/&#34;&gt;On the Promises of &amp;#39;High-Assurance&amp;#39; Cryptography&lt;/a&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.02.05&lt;/span&gt;
    &lt;/li&gt;
    
    
    &lt;li&gt;
      
      &lt;a href=&#34;https://symbolic.software/blog/2026-02-12-cryspen-response/&#34;&gt;We Found Bugs in Cryspen&amp;#39;s Verified Code&lt;/a&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.02.12&lt;/span&gt;
    &lt;/li&gt;
    
    
    &lt;li&gt;
      
      &lt;a href=&#34;https://symbolic.software/blog/2026-02-17-cryspen-mldsa/&#34;&gt;Even More Bugs in Cryspen&amp;#39;s libcrux: ML-DSA&lt;/a&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.02.17&lt;/span&gt;
    &lt;/li&gt;
    
    
    &lt;li class=&#34;current&#34; aria-current=&#34;page&#34;&gt;
      
      &lt;span class=&#34;title&#34;&gt;Cryspen&amp;#39;s Approach to TLS: A Critical Analysis&lt;/span&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.03.07&lt;/span&gt;
    &lt;/li&gt;
    
    
    &lt;li&gt;
      
      &lt;a href=&#34;https://symbolic.software/blog/2026-04-07-cryspen-hax/&#34;&gt;The Verification Facade: Structural Gaps in Hax&lt;/a&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.04.07&lt;/span&gt;
    &lt;/li&gt;
    
  &lt;/ol&gt;
  &lt;div class=&#34;series-nav-footer&#34;&gt;
    &lt;div class=&#34;series-nav-row&#34;&gt;
      &lt;span class=&#34;series-nav-rowlabel&#34;&gt;Papers&lt;/span&gt;
      &lt;a href=&#34;https://eprint.iacr.org/2026/192&#34;&gt;&lt;em&gt;Verification Theatre&lt;/em&gt;&lt;/a&gt;
      &lt;span class=&#34;series-nav-sep&#34;&gt;·&lt;/span&gt;
      &lt;a href=&#34;https://eprint.iacr.org/2026/670&#34;&gt;&lt;em&gt;Verification Facade&lt;/em&gt;&lt;/a&gt;
    &lt;/div&gt;
    &lt;div class=&#34;series-nav-row&#34;&gt;
      &lt;span class=&#34;series-nav-rowlabel&#34;&gt;Talk&lt;/span&gt;
      &lt;a href=&#34;https://www.youtube.com/watch?v=TdOXza1-M_4&#34;&gt;&lt;em&gt;High Assurance Cryptography and the Ethics of Disclosure (OSTIF)&lt;/em&gt;&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/nav&gt;

&lt;p&gt;This is the fourth installment in our series examining Cryspen&amp;rsquo;s cryptographic libraries. Over the past month, we have disclosed &lt;a href=&#34;https://symbolic.software/blog/2026-02-17-cryspen-mldsa/&#34;&gt;eleven vulnerabilities&lt;/a&gt; spanning post-quantum key encapsulation, digital signatures, hybrid public key encryption, and the formally verified F* specifications underlying them. We now turn to Cryspen&amp;rsquo;s TLS implementations: &lt;strong&gt;rustls-libcrux&lt;/strong&gt;, a cryptographic provider for rustls; &lt;strong&gt;Bertie&lt;/strong&gt;, a standalone TLS 1.3 implementation that has been the subject of formal verification research; and &lt;strong&gt;libcrux-iot&lt;/strong&gt;, an embedded-targeted fork of libcrux. Across these three projects, we found five more bugs, bringing the total to sixteen&amp;mdash;including a signature verification function that rejects 75% of valid signatures, encryption that silently drops its authentication tag, a TLS client that performs no certificate validation, remote denial-of-service vectors, and a FIPS 204 parameter that is off by a factor of eight.&lt;/p&gt;
&lt;h2 id=&#34;how-cryspen-handled-our-disclosures&#34;&gt;How Cryspen Handled our Disclosures&lt;/h2&gt;
&lt;p&gt;Before presenting our new findings, we document how Cryspen responded to our previous vulnerability disclosures.&lt;/p&gt;
&lt;p&gt;In January 2026, we began our audit work and opened our first issue on Cryspen&amp;rsquo;s GitHub (&lt;a href=&#34;https://github.com/cryspen/libcrux/issues/1299&#34;&gt;#1299&lt;/a&gt;). It was professional and submitted in good faith. When Cryspen&amp;rsquo;s team responded, we acknowledged that their perspective was legitimate, closed the issue cooperatively, and moved on.&lt;/p&gt;
&lt;p&gt;We then submitted four pull requests with tested fixes for security vulnerabilities, including a critical nonce reuse bug in hpke-rs (&lt;a href=&#34;https://github.com/cryspen/hpke-rs/pull/117&#34;&gt;PR #117&lt;/a&gt;, &lt;a href=&#34;https://github.com/cryspen/hpke-rs/pull/118&#34;&gt;PR #118&lt;/a&gt;) and issues in libcrux (&lt;a href=&#34;https://github.com/cryspen/libcrux/pull/1315&#34;&gt;PR #1315&lt;/a&gt;, &lt;a href=&#34;https://github.com/cryspen/libcrux/pull/1316&#34;&gt;PR #1316&lt;/a&gt;). These were submitted in the same professional manner as contributions from other external researchers such as Filippo Valsorda and &amp;ldquo;GuuJiang,&amp;rdquo; whom Cryspen had publicly thanked for their reports.&lt;/p&gt;
&lt;p&gt;Cryspen&amp;rsquo;s response to our contributions was markedly different:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;They closed all four of our pull requests.&lt;/li&gt;
&lt;li&gt;They publicly accused us of &amp;ldquo;putting the community at risk&amp;rdquo; and called our disclosures &amp;ldquo;irresponsible.&amp;rdquo;&lt;/li&gt;
&lt;li&gt;They blocked us from their GitHub organization, preventing us from submitting any further issues or pull requests.&lt;/li&gt;
&lt;li&gt;They then copied our fixes and applied them without credit, after blocking us and closing the PRs.&lt;/li&gt;
&lt;li&gt;They published security advisories that downplayed severity and obscured impact&amp;mdash;classifying our critical nonce reuse vulnerability as &amp;ldquo;medium severity&amp;rdquo; and describing fixes with vague labels like &amp;ldquo;fix context counter&amp;rdquo; and &amp;ldquo;avoid panic on AEADError&amp;rdquo; rather than honestly explaining the vulnerabilities and their impact.&lt;/li&gt;
&lt;li&gt;To date, no RustSec advisories exist for their most severe vulnerabilities.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The GitHub block had an immediate practical consequence: we discovered it when attempting to submit our fifth fix, a patch for an &lt;a href=&#34;https://github.com/nadimkobeissi/libcrux/tree/aesgcm-dos-fix&#34;&gt;AES-GCM denial-of-service crash in libcrux-psq&lt;/a&gt;. That fix remains available on our fork but could never be submitted upstream.&lt;/p&gt;
&lt;p&gt;We offered to continue disclosing our remaining findings, including those being revealed here today, as GitHub pull requests complete with tested fixes provided on the day of disclosure. &lt;strong&gt;Cryspen declined&lt;/strong&gt;, and then accused us of not giving them time to address vulnerabilities before public disclosure&amp;mdash;despite the fact that it was Cryspen who blocked our ability to submit fixes, after we had already disclosed four bugs complete with tested patches, free of charge.&lt;/p&gt;
&lt;p&gt;To be clear: &lt;strong&gt;after we submitted our initial four bug disclosures with tested fixes, we were blocked by Cryspen from submitting additional fixes, something we discovered when trying to submit our fifth fix.&lt;/strong&gt; In spite of this, Cryspen accused &lt;em&gt;us&lt;/em&gt; of not giving them time to fix the bugs we were disclosing.&lt;/p&gt;
&lt;p&gt;While Cryspen described our disclosures as &amp;ldquo;irresponsible&amp;rdquo; and &amp;ldquo;putting the community at risk&amp;rdquo;, we posit that it is their &lt;a href=&#34;https://bughunters.google.com/blog/formally-verified-post-quantum-algorithms&#34;&gt;active lobbying&lt;/a&gt; to get their cryptographic libraries merged in production into Signal, Google and OpenMLS&amp;rsquo;s stacks, despite their being aware of, and at times openly &lt;a href=&#34;https://github.com/cryspen/libcrux/issues/1335&#34;&gt;admitting their immaturity&lt;/a&gt;, that constitute irresponsible behavior. Furthermore, Cryspen has &lt;a href=&#34;https://github.com/cryspen/libcrux/issues/1275#event-21774379885&#34;&gt;often acted to downplay the severity of bugs that were found to impact applications such as Signal in production&lt;/a&gt;, and to this day has not issued complete or appropriate advisories for their most severe, critical vulnerabilities.&lt;/p&gt;
&lt;p&gt;Cryspen&amp;rsquo;s own &lt;a href=&#34;https://github.com/cryspen/libcrux/blob/main/CODE_OF_CONDUCT.md&#34;&gt;Code of Conduct&lt;/a&gt; commits to &amp;ldquo;accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience&amp;rdquo; and &amp;ldquo;focusing on what is best not just for us as individuals, but for the overall community.&amp;rdquo; We leave it to the reader to judge whether Cryspen&amp;rsquo;s handling of our disclosures lived up to these principles.&lt;/p&gt;
&lt;h2 id=&#34;findings&#34;&gt;Findings&lt;/h2&gt;
&lt;p&gt;Over the past month, we have published &lt;a href=&#34;https://symbolic.software/blog/2026-02-17-cryspen-mldsa/&#34;&gt;eleven vulnerabilities&lt;/a&gt; across Cryspen&amp;rsquo;s cryptographic libraries, spanning post-quantum key encapsulation, digital signatures, and hybrid public key encryption. Our findings included bugs in formally verified F* specifications, false proofs accepted by a build system that defaults to not checking them, and unproven axioms that render entire proof infrastructures unsound.&lt;/p&gt;
&lt;p&gt;We now expand our scope to Cryspen&amp;rsquo;s TLS implementations. Cryspen maintains two: &lt;strong&gt;rustls-libcrux&lt;/strong&gt;, a cryptographic provider for the rustls TLS library backed by libcrux primitives, and &lt;strong&gt;Bertie&lt;/strong&gt;, a standalone TLS 1.3 implementation. We also examined &lt;strong&gt;libcrux-iot&lt;/strong&gt;, Cryspen&amp;rsquo;s embedded-targeted fork of libcrux designed for constrained IoT devices.&lt;/p&gt;
&lt;p&gt;Across these three projects, we found five new bugs: a signature verification function that rejects approximately 75% of valid ECDSA P-256 signatures, a TLS 1.2 encryption path that silently drops the authentication tag&amp;mdash;eliminating all integrity protection&amp;mdash;a TLS 1.3 client that performs no certificate chain validation or hostname verification, remote denial-of-service vectors exploitable with a single malformed handshake message, and a FIPS 204 parameter that is off by a factor of eight.&lt;/p&gt;
&lt;h2 id=&#34;rustls-libcrux&#34;&gt;rustls-libcrux&lt;/h2&gt;
&lt;p&gt;The rustls-libcrux project provides a &lt;code&gt;CryptoProvider&lt;/code&gt; implementation for the rustls TLS library, replacing rustls&amp;rsquo;s default cryptographic backend with Cryspen&amp;rsquo;s libcrux. It implements signature verification, AEAD encryption and decryption, key exchange, and RSA signing&amp;mdash;the cryptographic foundation that the TLS protocol depends on for authentication, confidentiality, and integrity.&lt;/p&gt;
&lt;h3 id=&#34;finding-1-ecdsa-p-256-verification-rejects-75-of-valid-signatures&#34;&gt;Finding 1: ECDSA P-256 Verification Rejects ~75% of Valid Signatures&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;File:&lt;/strong&gt; &lt;code&gt;libcrux-provider/src/verify.rs&lt;/code&gt;, lines 57&amp;ndash;70.&lt;/p&gt;
&lt;p&gt;The ECDSA P-256 signature verification code extracts the &lt;code&gt;r&lt;/code&gt; and &lt;code&gt;s&lt;/code&gt; components from a DER-encoded signature and converts them to fixed-size 32-byte arrays:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-rust&#34; data-lang=&#34;rust&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;r&lt;/span&gt;: &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;u8&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;32&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sig&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;as_bytes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;().&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;try_into&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;().&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;map_err&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;_&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;InvalidSignature&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;?&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;s&lt;/span&gt;: &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;u8&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;32&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sig&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;as_bytes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;().&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;try_into&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;().&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;map_err&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;_&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;InvalidSignature&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;?&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;sig.r&lt;/code&gt; and &lt;code&gt;sig.s&lt;/code&gt; values are &lt;code&gt;der::asn1::Int&lt;/code&gt; types. The &lt;code&gt;Int::as_bytes()&lt;/code&gt; method returns the raw DER value bytes, which include a leading &lt;code&gt;0x00&lt;/code&gt; sign byte when the most significant bit of the integer is set. This is how DER encodes positive integers: if the MSB would otherwise indicate a negative number, a &lt;code&gt;0x00&lt;/code&gt; byte is prepended.&lt;/p&gt;
&lt;p&gt;For ECDSA P-256, &lt;code&gt;r&lt;/code&gt; and &lt;code&gt;s&lt;/code&gt; are 32-byte unsigned scalars. When the MSB is set&amp;mdash;which occurs with approximately 50% probability for each scalar&amp;mdash;DER encoding prepends &lt;code&gt;0x00&lt;/code&gt;, making &lt;code&gt;as_bytes()&lt;/code&gt; return 33 bytes. The &lt;code&gt;try_into::&amp;lt;[u8; 32]&amp;gt;()&lt;/code&gt; conversion then fails with a length mismatch, and the signature is rejected as invalid.&lt;/p&gt;
&lt;p&gt;The probability that at least one of &lt;code&gt;r&lt;/code&gt; or &lt;code&gt;s&lt;/code&gt; has its MSB set is $1 - 0.5 \cdot 0.5 = 75\%$. Three out of every four valid ECDSA P-256 signatures will be rejected by this code.&lt;/p&gt;
&lt;p&gt;This is the primary signature scheme for TLS 1.3 server authentication. A rustls client using the libcrux provider will fail to complete TLS handshakes with approximately 75% of servers, because the server&amp;rsquo;s valid certificate signature will be incorrectly rejected.&lt;/p&gt;
&lt;h3 id=&#34;finding-2-tls-12-encryption-drops-authentication-tag&#34;&gt;Finding 2: TLS 1.2 Encryption Drops Authentication Tag&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;File:&lt;/strong&gt; &lt;code&gt;libcrux-provider/src/aead.rs&lt;/code&gt;, lines 161&amp;ndash;163.&lt;/p&gt;
&lt;p&gt;The TLS 1.2 &lt;code&gt;MessageEncrypter&lt;/code&gt; implementation discards the AEAD authentication tag:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-rust&#34; data-lang=&#34;rust&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;libcrux&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;aead&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;encrypt&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;bp&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;payload&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;as_mut&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(),&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;iv&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;aad&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;map_err&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;_&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rustls&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;Error&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;EncryptError&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;map&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;_&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;OutboundOpaqueMessage&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;m&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;typ&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;m&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;version&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;payload&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;//       ^^^ tag discarded
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;libcrux::aead::encrypt()&lt;/code&gt; function returns the authentication tag as a separate value. The TLS 1.3 code path handles this correctly, appending the tag to the ciphertext:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-rust&#34; data-lang=&#34;rust&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;map&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;tag&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;payload&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;extend_from_slice&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;tag&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;as_ref&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;());&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;OutboundOpaqueMessage&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;m&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;typ&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;m&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;version&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;payload&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;})&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The TLS 1.2 path captures the tag as &lt;code&gt;_&lt;/code&gt; and drops it. The resulting TLS record contains only ciphertext with no integrity protection. The receiving peer will fail to decrypt because it expects a 16-byte authentication tag appended to the ciphertext, and the connection will fail.&lt;/p&gt;
&lt;p&gt;This is not a subtle vulnerability. Authenticated encryption without the authentication tag is just encryption&amp;mdash;the entire integrity guarantee of the TLS record layer is eliminated. The fix is one line: append the tag, exactly as the TLS 1.3 path already does.&lt;/p&gt;
&lt;h2 id=&#34;bertie&#34;&gt;Bertie&lt;/h2&gt;
&lt;p&gt;Bertie is Cryspen&amp;rsquo;s standalone TLS 1.3 implementation, written in Rust. It is described as a &amp;ldquo;minimal, low-level TLS 1.3 implementation&amp;rdquo; and has been the subject of formal verification research. Unlike rustls-libcrux, which delegates protocol logic to rustls, Bertie implements the TLS 1.3 handshake and record layer from scratch.&lt;/p&gt;
&lt;h3 id=&#34;finding-3-no-certificate-chain-validation-or-hostname-verification&#34;&gt;Finding 3: No Certificate Chain Validation or Hostname Verification&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Files:&lt;/strong&gt; &lt;code&gt;src/tls13cert.rs&lt;/code&gt;, lines 356&amp;ndash;373; &lt;code&gt;src/tls13handshake.rs&lt;/code&gt;, lines 318&amp;ndash;331.&lt;/p&gt;
&lt;p&gt;Bertie&amp;rsquo;s TLS 1.3 client accepts any certificate whose public key produces a valid CertificateVerify signature over the handshake transcript. No other validation is performed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No certificate chain validation (issuer signature verification)&lt;/li&gt;
&lt;li&gt;No hostname or SNI matching against Subject CN or Subject Alternative Name&lt;/li&gt;
&lt;li&gt;No validity period checking (expiration or not-before dates)&lt;/li&gt;
&lt;li&gt;No certificate extension validation (BasicConstraints, KeyUsage)&lt;/li&gt;
&lt;li&gt;No revocation checking (CRL or OCSP)&lt;/li&gt;
&lt;li&gt;No trust anchor verification (root CA store)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;code&gt;verification_key_from_cert()&lt;/code&gt; function at &lt;code&gt;tls13cert.rs:356&lt;/code&gt; parses a certificate by skipping version, serial number, signature algorithm, issuer, validity, and subject fields&amp;mdash;extracting only the SubjectPublicKeyInfo. The &lt;code&gt;server_name&lt;/code&gt; stored in &lt;code&gt;ServerPubInfo&lt;/code&gt; is populated during the handshake but never compared against any field in the certificate.&lt;/p&gt;
&lt;p&gt;The handshake function &lt;code&gt;put_server_signature()&lt;/code&gt; at &lt;code&gt;tls13handshake.rs:318&lt;/code&gt; extracts the public key using &lt;code&gt;verification_key_from_cert()&lt;/code&gt; and verifies the CertificateVerify signature over the transcript. If the signature is valid, the handshake proceeds. The certificate itself is never examined.&lt;/p&gt;
&lt;p&gt;An attacker can present a self-signed certificate for any domain&amp;mdash;or a certificate with no domain at all&amp;mdash;and Bertie will accept it, provided the CertificateVerify signature is valid with respect to the key in that certificate. This completely defeats server authentication. The attacker does not need to compromise a certificate authority, exploit a parsing vulnerability, or perform any cryptographic attack. They simply generate a key pair, create a self-signed certificate, and present it.&lt;/p&gt;
&lt;p&gt;This is not a missing feature in an early prototype. Certificate validation is the mechanism by which TLS clients distinguish legitimate servers from imposters. Without it, the encrypted channel provides confidentiality against passive observers but no authentication whatsoever&amp;mdash;the client cannot know who it is talking to.&lt;/p&gt;
&lt;h3 id=&#34;finding-4-remote-denial-of-service-via-unwrap-in-kem-operations&#34;&gt;Finding 4: Remote Denial of Service via &lt;code&gt;.unwrap()&lt;/code&gt; in KEM Operations&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;File:&lt;/strong&gt; &lt;code&gt;src/tls13crypto.rs&lt;/code&gt;, lines 710, 750, 752.&lt;/p&gt;
&lt;p&gt;Three &lt;code&gt;.unwrap()&lt;/code&gt; calls on fallible &lt;code&gt;decode()&lt;/code&gt; results will crash the process if given malformed key material:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-rust&#34; data-lang=&#34;rust&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// Line 710 — server-side, processes client&amp;#39;s key_share
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;pk&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;PublicKey&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;decode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;alg&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;libcrux_kem_algorithm&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;?&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;pk&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;declassify&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()).&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;unwrap&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// Line 750 — client-side, processes server&amp;#39;s key_share
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sk&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;PrivateKey&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;decode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;librux_algorithm&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sk&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;declassify&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()).&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;unwrap&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// Line 752 — client-side
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ct&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Ct&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;decode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;librux_algorithm&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ct&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;).&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;unwrap&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A remote attacker can crash a Bertie server by sending a ClientHello with a malformed &lt;code&gt;key_share&lt;/code&gt; extension. The malformed public key bytes reach &lt;code&gt;kem_encap()&lt;/code&gt; at &lt;code&gt;tls13handshake.rs:620&lt;/code&gt;, where &lt;code&gt;PublicKey::decode().unwrap()&lt;/code&gt; panics. Similarly, a malicious server can crash a Bertie client by sending a ServerHello with a malformed &lt;code&gt;key_share&lt;/code&gt;, reaching &lt;code&gt;kem_decap()&lt;/code&gt; at &lt;code&gt;tls13handshake.rs:243&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This is a single-packet denial of service. No handshake completion is required. No authentication is needed. One malformed message crashes the process.&lt;/p&gt;
&lt;p&gt;The fix is mechanical: replace &lt;code&gt;.unwrap()&lt;/code&gt; with &lt;code&gt;.map_err(|_| CRYPTO_ERROR)?&lt;/code&gt;. The correct error-handling pattern is used elsewhere in the same file.&lt;/p&gt;
&lt;h2 id=&#34;libcrux-iot&#34;&gt;libcrux-iot&lt;/h2&gt;
&lt;p&gt;libcrux-iot is Cryspen&amp;rsquo;s embedded-targeted fork of libcrux, designed for constrained IoT devices. It implements ML-KEM (FIPS 203), ML-DSA (FIPS 204), and SHA-3 (FIPS 202), with C code extracted via the Eurydice toolchain for deployment on bare-metal platforms.&lt;/p&gt;
&lt;h3 id=&#34;finding-5-ml-dsa-pre-hash-uses-wrong-output-length&#34;&gt;Finding 5: ML-DSA Pre-Hash Uses Wrong Output Length&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Files:&lt;/strong&gt; &lt;code&gt;ml-dsa/src/pre_hash.rs&lt;/code&gt;, line 40; &lt;code&gt;ml-dsa/src/ml_dsa_44.rs&lt;/code&gt;, line 121; &lt;code&gt;ml-dsa/src/ml_dsa_65.rs&lt;/code&gt;, line 134; &lt;code&gt;ml-dsa/src/ml_dsa_87.rs&lt;/code&gt;, line 121.&lt;/p&gt;
&lt;p&gt;FIPS 204 Table 2 specifies that the SHAKE-128 pre-hash output length for HashML-DSA is &lt;strong&gt;256 bytes&lt;/strong&gt; (2048 bits). The module docstring correctly states this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-rust&#34; data-lang=&#34;rust&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;//! pre-hash trait for SHAKE-128, with a digest length of 256 bytes.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But the implementation allocates a 32-byte buffer and asserts this size:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-rust&#34; data-lang=&#34;rust&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// pre_hash.rs:40
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;fm&#34;&gt;debug_assert_eq!&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;output&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;len&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(),&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;32&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;All call sites allocate 32-byte buffers:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-rust&#34; data-lang=&#34;rust&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// ml_dsa_44.rs:121, ml_dsa_65.rs:134, ml_dsa_87.rs:121
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;mut&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;pre_hash_buffer&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;u8&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;classify&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;32&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;];&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is not an edge case&amp;mdash;the pre-hash output length is a fundamental parameter of the HashML-DSA mode. SHAKE-128 at 32 bytes produces a different digest than SHAKE-128 at 256 bytes. Signatures produced by this implementation using the &lt;code&gt;sign_pre_hashed_shake128&lt;/code&gt; API will not verify against any FIPS 204-compliant implementation that uses the correct 256-byte output, and vice versa.&lt;/p&gt;
&lt;p&gt;The bug is present in the upstream &lt;code&gt;cryspen/libcrux&lt;/code&gt; as well, indicating it was inherited rather than introduced during the IoT fork. The pre-hashed NIST KAT files were generated by the same codebase, so they cannot detect the discrepancy. ACVP test vectors do not cover the HashML-DSA mode.&lt;/p&gt;
&lt;p&gt;Pure ML-DSA signing and verification&amp;mdash;the default and most commonly used mode&amp;mdash;is unaffected. Only the &lt;code&gt;sign_pre_hashed_shake128&lt;/code&gt; and &lt;code&gt;verify_pre_hashed_shake128&lt;/code&gt; APIs are impacted.&lt;/p&gt;
&lt;h2 id=&#34;the-scope-of-the-problem&#34;&gt;The Scope of the Problem&lt;/h2&gt;
&lt;p&gt;These five findings bring the total to sixteen bugs across Cryspen&amp;rsquo;s cryptographic ecosystem in one month. The pattern we have documented extends beyond any single library.&lt;/p&gt;
&lt;p&gt;In libcrux, we found &lt;a href=&#34;https://symbolic.software/blog/2026-02-12-cryspen-response/&#34;&gt;wrong F* specifications&lt;/a&gt;, &lt;a href=&#34;https://symbolic.software/blog/2026-02-12-cryspen-response/&#34;&gt;false proofs&lt;/a&gt;, and &lt;a href=&#34;https://symbolic.software/blog/2026-02-17-cryspen-mldsa/&#34;&gt;unsound axioms&lt;/a&gt;. In hpke-rs, we found &lt;a href=&#34;https://symbolic.software/blog/2026-02-05-cryspen/&#34;&gt;nonce reuse and broken forward secrecy&lt;/a&gt;. In rustls-libcrux, we find that TLS 1.3 connections will fail 75% of the time and TLS 1.2 encryption drops its authentication tag. In Bertie, we find a TLS implementation that does not validate certificates. In libcrux-iot, we find FIPS non-compliance in a post-quantum signature parameter.&lt;/p&gt;
&lt;p&gt;The rustls-libcrux findings are particularly instructive. This is a cryptographic provider&amp;mdash;its entire purpose is to correctly implement a small set of well-specified cryptographic operations for a well-tested TLS library. The operations are standard: ECDSA verification, AEAD encryption and decryption. The specifications are unambiguous. And yet the implementation rejects 75% of valid ECDSA signatures and drops authentication tags. These are not subtle corner cases. They are the primary code paths.&lt;/p&gt;
&lt;p&gt;Bertie&amp;rsquo;s missing certificate validation is harder to characterize charitably. Certificate chain validation is not an optional feature of TLS. It is the mechanism by which a client determines whether the server is who it claims to be. A TLS 1.3 implementation that omits this check has not implemented TLS in any meaningful sense&amp;mdash;it has implemented an encrypted channel to an unknown party.&lt;/p&gt;
&lt;p&gt;The fact that Bertie has been the subject of formal verification research makes this omission more concerning, not less. The verification effort focused on the handshake protocol&amp;rsquo;s cryptographic properties while the most basic authentication mechanism was absent. This is verification theatre in its purest form: proving properties of a protocol that cannot authenticate its peer.&lt;/p&gt;
&lt;p&gt;Across sixteen findings in six Cryspen projects, the pattern is consistent. The verified core is surrounded by an unverified periphery where basic engineering discipline is absent. The specifications that the verified code is checked against are themselves wrong. The build system defaults to not checking proofs. And the marketing claims make no distinction between what has been verified and what has not.&lt;/p&gt;
&lt;p&gt;Formal verification is a tool. Like any tool, its value depends on how it is applied. When it is applied selectively, communicated imprecisely, and used to justify claims that the evidence does not support, it becomes something worse than useless: it becomes a source of false confidence. Users who trust &amp;ldquo;formally verified&amp;rdquo; to mean &amp;ldquo;safe to deploy&amp;rdquo; will deploy code that rejects valid signatures, drops authentication tags, and accepts certificates from anyone.&lt;/p&gt;





&lt;nav class=&#34;series-nav&#34; aria-label=&#34;Cryspen series (bottom)&#34;&gt;
  &lt;header class=&#34;series-nav-header&#34;&gt;
    &lt;span class=&#34;series-nav-label&#34;&gt;▶ Cryspen Series&lt;/span&gt;
    &lt;span class=&#34;series-nav-count&#34;&gt;5 posts · 2 papers · 1 talk&lt;/span&gt;
  &lt;/header&gt;
  &lt;ol class=&#34;series-nav-list&#34;&gt;
    
    
    &lt;li&gt;
      
      &lt;a href=&#34;https://symbolic.software/blog/2026-02-05-cryspen/&#34;&gt;On the Promises of &amp;#39;High-Assurance&amp;#39; Cryptography&lt;/a&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.02.05&lt;/span&gt;
    &lt;/li&gt;
    
    
    &lt;li&gt;
      
      &lt;a href=&#34;https://symbolic.software/blog/2026-02-12-cryspen-response/&#34;&gt;We Found Bugs in Cryspen&amp;#39;s Verified Code&lt;/a&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.02.12&lt;/span&gt;
    &lt;/li&gt;
    
    
    &lt;li&gt;
      
      &lt;a href=&#34;https://symbolic.software/blog/2026-02-17-cryspen-mldsa/&#34;&gt;Even More Bugs in Cryspen&amp;#39;s libcrux: ML-DSA&lt;/a&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.02.17&lt;/span&gt;
    &lt;/li&gt;
    
    
    &lt;li class=&#34;current&#34; aria-current=&#34;page&#34;&gt;
      
      &lt;span class=&#34;title&#34;&gt;Cryspen&amp;#39;s Approach to TLS: A Critical Analysis&lt;/span&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.03.07&lt;/span&gt;
    &lt;/li&gt;
    
    
    &lt;li&gt;
      
      &lt;a href=&#34;https://symbolic.software/blog/2026-04-07-cryspen-hax/&#34;&gt;The Verification Facade: Structural Gaps in Hax&lt;/a&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.04.07&lt;/span&gt;
    &lt;/li&gt;
    
  &lt;/ol&gt;
  &lt;div class=&#34;series-nav-footer&#34;&gt;
    &lt;div class=&#34;series-nav-row&#34;&gt;
      &lt;span class=&#34;series-nav-rowlabel&#34;&gt;Papers&lt;/span&gt;
      &lt;a href=&#34;https://eprint.iacr.org/2026/192&#34;&gt;&lt;em&gt;Verification Theatre&lt;/em&gt;&lt;/a&gt;
      &lt;span class=&#34;series-nav-sep&#34;&gt;·&lt;/span&gt;
      &lt;a href=&#34;https://eprint.iacr.org/2026/670&#34;&gt;&lt;em&gt;Verification Facade&lt;/em&gt;&lt;/a&gt;
    &lt;/div&gt;
    &lt;div class=&#34;series-nav-row&#34;&gt;
      &lt;span class=&#34;series-nav-rowlabel&#34;&gt;Talk&lt;/span&gt;
      &lt;a href=&#34;https://www.youtube.com/watch?v=TdOXza1-M_4&#34;&gt;&lt;em&gt;High Assurance Cryptography and the Ethics of Disclosure (OSTIF)&lt;/em&gt;&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/nav&gt;

</content:encoded>
      <category>Research</category>
      <category>Formal Verification</category>
      <category>Protocol Design</category>
      
    </item>
    
    <item>
      <title>Making Verifpal Easier to Reason About</title>
      <link>https://symbolic.software/blog/2026-03-01-verifpal-engine-redesign/</link>
      <pubDate>Sun, 01 Mar 2026 00:00:00 &#43;0000</pubDate>
      <guid>https://symbolic.software/blog/2026-03-01-verifpal-engine-redesign/</guid>
      <description>Verifpal&#39;s analysis engine has been redesigned with a unified equational theory, provenance-tagged values, a formally grounded deduction loop, and a bounded-depth search that runs 3x faster — plus updated tooling across the board.</description>
      <content:encoded>&lt;p&gt;A couple of weeks after &lt;a href=&#34;https://symbolic.software/blog/2026-02-23-verifpal-rust/&#34;&gt;releasing the Rust rewrite&lt;/a&gt;, we&amp;rsquo;ve shipped a deep architectural redesign of Verifpal&amp;rsquo;s analysis engine in Verifpal version 0.50.0. The observable behavior hasn&amp;rsquo;t changed, but the engine is now faster (3x on the full test suite), formally cleaner, and significantly easier to reason about. This post explains what changed and why, starting from the basics.&lt;/p&gt;
&lt;h2 id=&#34;what-verifpal-does-and-why-the-engine-matters&#34;&gt;What Verifpal Does, and Why the Engine Matters&lt;/h2&gt;
&lt;p&gt;Verifpal analyzes cryptographic protocols. You describe a protocol — who talks to whom, what gets encrypted, what gets signed — and Verifpal checks whether an attacker can break its security properties: steal secrets, forge messages, impersonate participants.&lt;/p&gt;
&lt;p&gt;The engine that does this analysis has three jobs:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Deduction&lt;/strong&gt;: figure out everything the attacker can learn from observing (and tampering with) the protocol.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Search&lt;/strong&gt;: systematically try different attacker strategies — replacing values on the wire, injecting forged messages.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Query evaluation&lt;/strong&gt;: check whether any security property has been violated.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The old engine did all three jobs correctly, but the way it organized its work made it hard to &lt;em&gt;see&lt;/em&gt; why it was correct. The redesign doesn&amp;rsquo;t change what the engine computes. It changes how the computation is structured, so that correctness becomes obvious rather than subtle.&lt;/p&gt;
&lt;h2 id=&#34;change-1-one-place-for-all-the-rules&#34;&gt;Change 1: One Place for All the Rules&lt;/h2&gt;
&lt;h3 id=&#34;the-problem&#34;&gt;The Problem&lt;/h3&gt;
&lt;p&gt;A protocol verifier is only as good as its equational theory — the formal rules that define what cryptographic operations do. In Verifpal, these rules say things like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;DEC(k, ENC(k, m)) → m                  [given: k]
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;AEAD_DEC(k, AEAD_ENC(k, m, ad), ad) → m [given: k, ad]
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;SIGNVERIF(G^sk, msg, SIGN(sk, msg)) → nil [verification check]
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;G^a^b = G^b^a                           [DH commutativity]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The first rule says: if you have the key &lt;code&gt;k&lt;/code&gt; and you see &lt;code&gt;ENC(k, m)&lt;/code&gt; — a value encrypted with that key — you can recover the plaintext &lt;code&gt;m&lt;/code&gt;. The Diffie-Hellman commutativity rule says: &lt;code&gt;G^a^b&lt;/code&gt; and &lt;code&gt;G^b^a&lt;/code&gt; are the same value, which is the mathematical property that makes key agreement work.&lt;/p&gt;
&lt;p&gt;Before this redesign, these rules were &lt;em&gt;scattered across five files&lt;/em&gt;. The declarative specifications lived in &lt;code&gt;primitive/spec.rs&lt;/code&gt;. The code that interpreted those specs and checked &amp;ldquo;can the attacker actually do this?&amp;rdquo; lived in &lt;code&gt;possible.rs&lt;/code&gt;. The code that applied the rules during protocol execution lived in &lt;code&gt;rewrite.rs&lt;/code&gt;. DH commutativity was handled in &lt;code&gt;equivalence.rs&lt;/code&gt;. And the filtering of constructible values for the attacker lived in &lt;code&gt;inject.rs&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This meant that the answer to &amp;ldquo;what can the attacker do?&amp;rdquo; didn&amp;rsquo;t have a single location in the code. You had to hold all five files in your head simultaneously.&lt;/p&gt;
&lt;h3 id=&#34;the-solution&#34;&gt;The Solution&lt;/h3&gt;
&lt;p&gt;A new module, &lt;code&gt;theory.rs&lt;/code&gt;, now centralizes every derivation operation. The complete equational theory — all 14 rewrite rules for 21 cryptographic primitives — is listed in a single doc comment at the top of the file:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;DEC(k, ENC(k, m)) → m                                    [given: k]
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;AEAD_DEC(k, AEAD_ENC(k, m, ad), ad) → m                  [given: k, ad]
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;AEAD_ENC(k, m, ad) → ad                                   [passive]
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ENC(k, m) → m                                             [given: k]
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;BLIND(factor, m) → m                                      [given: factor]
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;PKE_DEC(sk, PKE_ENC(G^sk, m)) → m                         [given: sk]
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;PKE_ENC(G^sk, m) → m                                      [given: sk (via G^sk)]
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;SIGNVERIF(G^sk, msg, SIGN(sk, msg)) → nil                 [rewrite: def check]
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;RINGSIGNVERIF(G^sk, ..., RINGSIGN(sk, ...)) → nil         [rewrite: def check]
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;UNBLIND(factor, SIGN(sk, BLIND(factor, m)), m) → SIGN(sk, m) [rewrite]
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;SHAMIR_SPLIT(s)[i] + SHAMIR_SPLIT(s)[j] → s               [recompose: 2-of-3]
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;SHAMIR_JOIN(SHAMIR_SPLIT(s)[i], SHAMIR_SPLIT(s)[j]) → s   [rebuild]
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;CONCAT(a, b, ...) → a, b, ...                             [passive: reveals args]
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;G^a^b = G^b^a                                              [DH commutativity]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The eight operations the attacker can perform — decompose, passive-decompose, recompose, reconstruct (primitive), reconstruct (equation), rewrite, rebuild, and password extraction — are all implemented in this one file. The old &lt;code&gt;possible.rs&lt;/code&gt; became a one-line re-export shim. No callers needed to change their imports.&lt;/p&gt;
&lt;p&gt;The practical benefit: to understand what the attacker can do, read one file. To add a new cryptographic primitive, edit one file (&lt;code&gt;spec.rs&lt;/code&gt;). The engine picks up the new rules automatically.&lt;/p&gt;
&lt;h2 id=&#34;change-2-the-three-phase-pipeline&#34;&gt;Change 2: The Three-Phase Pipeline&lt;/h2&gt;
&lt;h3 id=&#34;the-problem-1&#34;&gt;The Problem&lt;/h3&gt;
&lt;p&gt;When analyzing a protocol, the engine needs to do three things for each principal (participant): resolve all symbolic values, figure out what the attacker learns, and check security queries. The old engine interleaved these steps:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;for each principal:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    resolve values
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    loop:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        check queries        ← interleaved!
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        try one deduction step
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        if no progress: stop
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    check queries again
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The query check inside the deduction loop meant that the engine could exit early — if all queries happened to resolve mid-deduction, why keep going? This was a performance optimization, and it worked. But it had a subtle cost: the deduction loop was no longer a clean mathematical object. Its behavior depended on &lt;em&gt;when&lt;/em&gt; queries resolved, which depended on &lt;em&gt;what order&lt;/em&gt; values were processed, which was an implementation detail rather than a formal property.&lt;/p&gt;
&lt;h3 id=&#34;the-solution-separation-of-concerns&#34;&gt;The Solution: Separation of Concerns&lt;/h3&gt;
&lt;p&gt;The pipeline is now three explicit phases:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-rust&#34; data-lang=&#34;rust&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;fn&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;verify_standard_run&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ctx&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;km&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;principal_states&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;stage&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ps&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;in&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;principal_states&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// Phase 1: Trace generation
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ps_resolved&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;generate_trace&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ctx&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;km&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ps&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;attacker&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;?&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// Phase 2: Knowledge closure (monotone fixed-point)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;compute_knowledge_closure&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ctx&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;km&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ps_resolved&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;stage&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;?&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// Phase 3: Query evaluation
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;verify_resolve_queries&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ctx&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;km&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ps_resolved&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;?&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Phase 1&lt;/strong&gt; (&lt;code&gt;generate_trace&lt;/code&gt;) does all the mechanical work: resolve symbolic constant references to concrete values, record which slots differ from the original protocol trace, inject structural templates into the attacker&amp;rsquo;s knowledge, apply cryptographic rewrite rules (like &lt;code&gt;DEC(k, ENC(k, m)) → m&lt;/code&gt;), and check that nothing went wrong.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase 2&lt;/strong&gt; (&lt;code&gt;compute_knowledge_closure&lt;/code&gt;) is a pure deduction loop. It runs the attacker&amp;rsquo;s derivation rules until nothing new can be learned — no query checks, no early exits, no side effects beyond expanding the attacker&amp;rsquo;s knowledge set. This is now a textbook &lt;em&gt;monotone fixed-point computation&lt;/em&gt;, which gives us a clean correctness argument.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase 3&lt;/strong&gt; (&lt;code&gt;verify_resolve_queries&lt;/code&gt;) checks all security queries against the final, complete attacker knowledge.&lt;/p&gt;
&lt;h3 id=&#34;why-fixed-points-matter&#34;&gt;Why Fixed Points Matter&lt;/h3&gt;
&lt;p&gt;This might sound like an academic distinction, but it has real consequences. A &lt;strong&gt;monotone fixed point&lt;/strong&gt; is a concept from order theory. Informally: if you have a function F that only ever &lt;em&gt;adds&lt;/em&gt; to a set (never removes), and the set has a maximum size, then repeatedly applying F will eventually reach a stable state where applying F doesn&amp;rsquo;t change anything. That stable state is the &lt;em&gt;least fixed point&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The attacker&amp;rsquo;s deduction rules are monotone: the attacker only ever learns new values, never forgets them. The set of derivable values is finite (bounded by the protocol model). So the deduction loop is computing a least fixed point, and the &lt;strong&gt;Knaster-Tarski theorem&lt;/strong&gt; guarantees it converges. Every value in the result is genuinely derivable. No value is missed. These properties hold regardless of evaluation order.&lt;/p&gt;
&lt;p&gt;The old interleaved loop &lt;em&gt;approximated&lt;/em&gt; this, but the early exits and query checks made the argument indirect. Now it&amp;rsquo;s immediate: &lt;code&gt;compute_knowledge_closure&lt;/code&gt; is a Kleene iteration over a monotone function, and the Knaster-Tarski theorem does the rest.&lt;/p&gt;
&lt;h2 id=&#34;change-3-provenance-tagged-values&#34;&gt;Change 3: Provenance-Tagged Values&lt;/h2&gt;
&lt;h3 id=&#34;the-problem-2&#34;&gt;The Problem&lt;/h3&gt;
&lt;p&gt;Verifpal&amp;rsquo;s analysis is fundamentally about &lt;em&gt;perspective&lt;/em&gt;. When an attacker intercepts and modifies a message on the wire, the sender and receiver see different things. Alice computes &lt;code&gt;k = HASH(secret)&lt;/code&gt; and sends it to Bob. The attacker replaces &lt;code&gt;k&lt;/code&gt; with &lt;code&gt;HASH(attacker_value)&lt;/code&gt;. Alice still &amp;ldquo;sees&amp;rdquo; her original &lt;code&gt;k&lt;/code&gt;. Bob sees the attacker&amp;rsquo;s version. If the engine gets this wrong — if Alice somehow &amp;ldquo;sees&amp;rdquo; the tampered value in her own subsequent computations — authentication queries produce false positives.&lt;/p&gt;
&lt;p&gt;The old engine tracked this with three separate copies of every value:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-rust&#34; data-lang=&#34;rust&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// Old design
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;SlotValues&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;assigned&lt;/span&gt;: &lt;span class=&#34;nc&#34;&gt;Value&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// current value (after mutation + rewrite)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;before_rewrite&lt;/span&gt;: &lt;span class=&#34;nc&#34;&gt;Value&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// after mutation, before rewrite
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;before_mutate&lt;/span&gt;: &lt;span class=&#34;nc&#34;&gt;Value&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;   &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// original (before attacker tampering)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;mutated&lt;/span&gt;: &lt;span class=&#34;kt&#34;&gt;bool&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;creator&lt;/span&gt;: &lt;span class=&#34;nc&#34;&gt;PrincipalId&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sender&lt;/span&gt;: &lt;span class=&#34;nc&#34;&gt;PrincipalId&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rewritten&lt;/span&gt;: &lt;span class=&#34;kt&#34;&gt;bool&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The decision about which copy a principal &amp;ldquo;sees&amp;rdquo; was made by a function called &lt;code&gt;should_use_before_mutate&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-rust&#34; data-lang=&#34;rust&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// Old logic
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;fn&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;should_use_before_mutate&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;bp&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;: &lt;span class=&#34;kt&#34;&gt;usize&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;-&amp;gt; &lt;span class=&#34;kt&#34;&gt;bool&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;bp&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;values&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;].&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;creator&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;==&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;bp&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;||&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;bp&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;meta&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;].&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;known&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;||&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;bp&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;meta&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;].&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;wire&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;contains&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;bp&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;||&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;bp&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;values&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;].&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;mutated&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This worked, but the &lt;em&gt;meaning&lt;/em&gt; was indirect. You had to know that &lt;code&gt;creator == self.id&lt;/code&gt; means &amp;ldquo;I made this myself, so I see my version.&amp;rdquo; You had to know that &lt;code&gt;!mutated&lt;/code&gt; means &amp;ldquo;the attacker didn&amp;rsquo;t touch it, so there&amp;rsquo;s only one version.&amp;rdquo; The fields &lt;code&gt;creator&lt;/code&gt;, &lt;code&gt;sender&lt;/code&gt;, and &lt;code&gt;mutated&lt;/code&gt; were scattered alongside the value copies, with no explicit connection between them.&lt;/p&gt;
&lt;h3 id=&#34;the-solution-provenance&#34;&gt;The Solution: Provenance&lt;/h3&gt;
&lt;p&gt;The redesign introduces an explicit &lt;code&gt;Provenance&lt;/code&gt; record:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-rust&#34; data-lang=&#34;rust&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;Provenance&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;creator&lt;/span&gt;: &lt;span class=&#34;nc&#34;&gt;PrincipalId&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sender&lt;/span&gt;: &lt;span class=&#34;nc&#34;&gt;PrincipalId&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;attacker_tainted&lt;/span&gt;: &lt;span class=&#34;kt&#34;&gt;bool&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;SlotValues&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;value&lt;/span&gt;: &lt;span class=&#34;nc&#34;&gt;Value&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;           &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// current canonical value
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;original&lt;/span&gt;: &lt;span class=&#34;nc&#34;&gt;Value&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// the principal&amp;#39;s own computation
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;pre_rewrite&lt;/span&gt;: &lt;span class=&#34;nc&#34;&gt;Value&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;     &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// forensic snapshot (for error messages)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rewritten&lt;/span&gt;: &lt;span class=&#34;kt&#34;&gt;bool&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;provenance&lt;/span&gt;: &lt;span class=&#34;nc&#34;&gt;Provenance&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The visibility decision is now a direct provenance check:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-rust&#34; data-lang=&#34;rust&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;fn&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;should_use_original&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;bp&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;: &lt;span class=&#34;kt&#34;&gt;usize&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;-&amp;gt; &lt;span class=&#34;kt&#34;&gt;bool&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;bp&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;values&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;].&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;provenance&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;attacker_tainted&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;||&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;bp&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;values&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;].&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;provenance&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;creator&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;==&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;bp&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;||&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;bp&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;meta&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;].&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;known&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;||&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;bp&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;meta&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;].&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;wire&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;contains&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;bp&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The semantic difference is subtle but important. The old code asked: &amp;ldquo;was this value mutated, and who created it?&amp;rdquo; The new code asks: &amp;ldquo;is this value tainted by the attacker?&amp;rdquo; The answer is the same — the conditions haven&amp;rsquo;t changed — but the question is phrased in terms of &lt;em&gt;what happened to the value&lt;/em&gt; rather than &lt;em&gt;what fields happen to be set&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;This also cleaned up the naming throughout the codebase: &lt;code&gt;assigned&lt;/code&gt; → &lt;code&gt;value&lt;/code&gt;, &lt;code&gt;before_mutate&lt;/code&gt; → &lt;code&gt;original&lt;/code&gt;, &lt;code&gt;before_rewrite&lt;/code&gt; → &lt;code&gt;pre_rewrite&lt;/code&gt;, &lt;code&gt;mutated&lt;/code&gt; → &lt;code&gt;provenance.attacker_tainted&lt;/code&gt;, &lt;code&gt;set_assigned()&lt;/code&gt; → &lt;code&gt;set_value()&lt;/code&gt;. These renamings touched every module, but each change made the code read more naturally.&lt;/p&gt;
&lt;h2 id=&#34;change-4-bounded-depth-search&#34;&gt;Change 4: Bounded-Depth Search&lt;/h2&gt;
&lt;h3 id=&#34;the-problem-3&#34;&gt;The Problem&lt;/h3&gt;
&lt;p&gt;Verifpal&amp;rsquo;s active attacker analysis works by trying mutations: replacing wire values with attacker-controlled alternatives, then checking whether the attacker can derive new secrets. The old engine used a &lt;strong&gt;ten-stage search&lt;/strong&gt; controlled by seven empirical constants:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-rust&#34; data-lang=&#34;rust&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// Old design
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;const&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;no&#34;&gt;BUDGET&lt;/span&gt;: &lt;span class=&#34;nc&#34;&gt;StageBudget&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;StageBudget&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;exhaustion_threshold&lt;/span&gt;: &lt;span class=&#34;mi&#34;&gt;6&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;max_stage&lt;/span&gt;: &lt;span class=&#34;mi&#34;&gt;10&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;max_subset_weight&lt;/span&gt;: &lt;span class=&#34;mi&#34;&gt;3&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;max_subsets_per_weight&lt;/span&gt;: &lt;span class=&#34;mi&#34;&gt;150&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;max_weight1_mutations&lt;/span&gt;: &lt;span class=&#34;mi&#34;&gt;150&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;max_mutations_per_subset&lt;/span&gt;: &lt;span class=&#34;mi&#34;&gt;50000&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;max_full_product&lt;/span&gt;: &lt;span class=&#34;mi&#34;&gt;50000&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;max_scan_budget&lt;/span&gt;: &lt;span class=&#34;mi&#34;&gt;80000&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;};&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;These numbers worked — Verifpal found attacks in Signal, Scuttlebutt, TLS 1.3, and 94 test models. But several independent dimensions were conflated into a single &amp;ldquo;stage&amp;rdquo; counter. The stage number simultaneously controlled:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Which values to mutate&lt;/strong&gt;: stages 1–3 only offered minimal replacements (nil, G^nil); stage 4+ offered all known values&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;How deeply to nest injected values&lt;/strong&gt;: stage 5+ enabled recursive injection&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Which primitives to consider&lt;/strong&gt;: stages 0–1 blocked primitive injection; stage 2 blocked &amp;ldquo;explosive&amp;rdquo; primitives (HASH, HKDF, CONCAT)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;When to give up&lt;/strong&gt;: exhaustion after stage 6&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This meant you couldn&amp;rsquo;t answer a simple question: &amp;ldquo;what class of attacks does Verifpal guarantee to find?&amp;rdquo; The answer depended on stage progression, budget exhaustion, and the interaction of four separate thresholds.&lt;/p&gt;
&lt;p&gt;On top of this, a special-case function called &lt;code&gt;verify_active_equation_bypass&lt;/code&gt; ran before the main search. It directly tried replacing Diffie-Hellman public keys with the attacker&amp;rsquo;s own key — the canonical man-in-the-middle attack. This was necessary because the staged search might not try this specific combination within its budget. But it was a symptom: if the general search needed a hand-coded shortcut for the most common attack class, the general search had blind spots.&lt;/p&gt;
&lt;h3 id=&#34;the-solution-one-parameter&#34;&gt;The Solution: One Parameter&lt;/h3&gt;
&lt;p&gt;The new search is controlled by a single depth parameter:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-rust&#34; data-lang=&#34;rust&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;SearchConfig&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;depth&lt;/span&gt;: &lt;span class=&#34;kt&#34;&gt;usize&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;                &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// Default: 3
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;max_subsets_per_weight&lt;/span&gt;: &lt;span class=&#34;kt&#34;&gt;usize&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;max_weight1_mutations&lt;/span&gt;: &lt;span class=&#34;kt&#34;&gt;usize&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;max_mutations_per_subset&lt;/span&gt;: &lt;span class=&#34;kt&#34;&gt;usize&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;max_full_product&lt;/span&gt;: &lt;span class=&#34;kt&#34;&gt;usize&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;max_scan_budget&lt;/span&gt;: &lt;span class=&#34;kt&#34;&gt;u32&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;At depth &lt;code&gt;d&lt;/code&gt;, the engine explores all k-subsets for k ≤ d, with all attacker-known values as potential replacements, and recursive injection nesting bounded by max(0, d − 1). The coverage guarantee is precise:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;At depth d, Verifpal explores all attacker strategies involving at most d simultaneous value substitutions, with injected values of nesting depth at most d.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;The ten stages collapsed into three depth levels. The &lt;code&gt;STAGE_MUTATION_EXPANSION&lt;/code&gt; constant was deleted — all mutations are now available at every depth. The &lt;code&gt;STAGE_RECURSIVE_INJECTION&lt;/code&gt; constant and &lt;code&gt;inject_primitive_stage_restricted&lt;/code&gt; function were deleted — injection eligibility is controlled solely by the depth parameter.&lt;/p&gt;
&lt;p&gt;The equation bypass was deleted entirely. Replacing a received &lt;code&gt;G^sk&lt;/code&gt; with &lt;code&gt;G^nil&lt;/code&gt; is a natural first-order mutation at depth 1: &lt;code&gt;G^nil&lt;/code&gt; is always in the attacker&amp;rsquo;s knowledge set (it&amp;rsquo;s the attacker&amp;rsquo;s own DH public key), and equation-valued slots accept equation replacements. No special case needed. The fact that the old engine required one tells you the staged search was leaving holes that were patched ad hoc.&lt;/p&gt;
&lt;h3 id=&#34;the-result&#34;&gt;The Result&lt;/h3&gt;
&lt;p&gt;The numbers speak for themselves:&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;&lt;/th&gt;
          &lt;th&gt;Old (10-stage)&lt;/th&gt;
          &lt;th&gt;New (depth-3)&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;strong&gt;Parameters&lt;/strong&gt;&lt;/td&gt;
          &lt;td&gt;7 empirical constants&lt;/td&gt;
          &lt;td&gt;1 depth + 5 budget caps&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;strong&gt;Stages/depths&lt;/strong&gt;&lt;/td&gt;
          &lt;td&gt;10&lt;/td&gt;
          &lt;td&gt;3&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;strong&gt;Equation bypass&lt;/strong&gt;&lt;/td&gt;
          &lt;td&gt;Special-case function&lt;/td&gt;
          &lt;td&gt;Natural depth-1 mutation&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;strong&gt;Test suite time&lt;/strong&gt;&lt;/td&gt;
          &lt;td&gt;~11s&lt;/td&gt;
          &lt;td&gt;~3.5s&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;strong&gt;Coverage guarantee&lt;/strong&gt;&lt;/td&gt;
          &lt;td&gt;Implicit&lt;/td&gt;
          &lt;td&gt;Explicit (k ≤ d substitutions, nesting ≤ d)&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;strong&gt;Attacks found&lt;/strong&gt;&lt;/td&gt;
          &lt;td&gt;All 147 tests&lt;/td&gt;
          &lt;td&gt;All 147 tests (identical)&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;3x faster, clearer coverage semantics, less code. The default depth of 3 finds every attack that the 10-stage system found, in a third of the time.&lt;/p&gt;
&lt;h2 id=&#34;how-it-all-fits-together&#34;&gt;How It All Fits Together&lt;/h2&gt;
&lt;p&gt;These four changes are not independent. They build on each other:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The unified theory&lt;/strong&gt; gives the engine a single, auditable rule set. When the deduction loop asks &amp;ldquo;what can the attacker derive?&amp;rdquo;, the answer comes from one module with one doc comment listing every rule.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The three-phase pipeline&lt;/strong&gt; separates &lt;em&gt;what the attacker learns&lt;/em&gt; from &lt;em&gt;what security properties hold&lt;/em&gt;. The deduction loop runs to completion as a clean fixed point; query evaluation happens after, on the final knowledge set.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Provenance tagging&lt;/strong&gt; makes the visibility decision — which value a principal perceives — an explicit metadata check rather than an indirect inference from scattered boolean fields.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The bounded-depth search&lt;/strong&gt; eliminates the staged complexity and the equation bypass special case, giving a clear coverage guarantee parameterized by a single number.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Each change made the next one easier. The unified theory simplified the deduction loop. The clean deduction loop enabled the phase separation. The phase separation made the provenance changes straightforward to verify. And the provenance model made the search redesign cleaner because mutation injection has simpler semantics when taint is explicit.&lt;/p&gt;
&lt;h2 id=&#34;updated-tooling&#34;&gt;Updated Tooling&lt;/h2&gt;
&lt;p&gt;The engine redesign was a good occasion to bring the rest of the Verifpal ecosystem up to date.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;VSCode extension.&lt;/strong&gt; The &lt;a href=&#34;https://github.com/symbolicsoft/verifpal-vscode&#34;&gt;VSCode extension&lt;/a&gt; has been updated to work with the Rust rewrite, restoring hover documentation, document formatting, attacker analysis with inline result decorations, and protocol diagram rendering. The interface between the extension and the Rust binary was redesigned to be simpler than the original — pre-formatted display strings replace the old JSON round-trip, cutting two subcommands and eliminating the need for JSON deserialization on the Rust side.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Neovim extension.&lt;/strong&gt; There&amp;rsquo;s a new &lt;a href=&#34;https://github.com/symbolicsoft/verifpal-nvim&#34;&gt;Neovim extension&lt;/a&gt; with syntax highlighting, inline diagnostics that mark passing and failing queries directly in the sign column, document formatting via &lt;code&gt;verifpal pretty&lt;/code&gt;, and hover documentation for all 21 primitives, all query types, and all keywords. It uses Neovim&amp;rsquo;s native diagnostic API — no LSP server required — and installs with a single line in lazy.nvim or packer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Verifpal Manual.&lt;/strong&gt; &lt;a href=&#34;https://static.verifpal.com/manual.pdf&#34;&gt;Print 18&lt;/a&gt; of the Verifpal Manual covers all of the changes described in this post — the unified theory, the three-phase pipeline, provenance tagging, and the bounded-depth search — alongside the existing reference material.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Verifpal Workbench.&lt;/strong&gt; The &lt;a href=&#34;https://verifpal.com/workbench/&#34;&gt;browser-based Workbench&lt;/a&gt; has been updated with the latest WASM build, incorporating the engine redesign and all new primitives.&lt;/p&gt;
</content:encoded>
      <category>Software</category>
      <category>Research</category>
      <category>Verifpal</category>
      <category>Formal Verification</category>
      
    </item>
    
    <item>
      <title>Verifpal Workbench: Protocol Analysis in Your Browser</title>
      <link>https://symbolic.software/blog/2026-02-24-verifpal-workbench/</link>
      <pubDate>Tue, 24 Feb 2026 00:00:00 &#43;0000</pubDate>
      <guid>https://symbolic.software/blog/2026-02-24-verifpal-workbench/</guid>
      <description>Verifpal now runs entirely in the browser via WebAssembly. The new Workbench at verifpal.com/workbench lets anyone write, verify, and visualize cryptographic protocol models with zero installation.</description>
      <content:encoded>&lt;p&gt;Yesterday we &lt;a href=&#34;https://symbolic.software/blog/2026-02-23-verifpal-rust/&#34;&gt;announced the Rust rewrite of Verifpal&lt;/a&gt;. Today we&amp;rsquo;re shipping something that the rewrite made possible: &lt;strong&gt;&lt;a href=&#34;https://verifpal.com/workbench&#34;&gt;Verifpal Workbench&lt;/a&gt;&lt;/strong&gt;, a browser-based environment where you can write, verify, and visualize cryptographic protocol models — with nothing to install.&lt;/p&gt;
&lt;p&gt;The entire Verifpal verification engine now compiles to a 350 KB WebAssembly binary. Open a browser tab, write a model, click Verify. The analysis runs client-side on your machine. No server, no upload, no account.&lt;/p&gt;
&lt;h2 id=&#34;why-a-browser-tool&#34;&gt;Why a Browser Tool?&lt;/h2&gt;
&lt;p&gt;Verifpal was designed to make formal verification accessible to engineers and students who don&amp;rsquo;t have a background in formal methods. But &amp;ldquo;accessible&amp;rdquo; has always meant &amp;ldquo;download a binary and learn the command line&amp;rdquo; — which is still a barrier, especially in a classroom setting.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ve wanted to fix this for years. The old Go codebase used &lt;code&gt;cgo&lt;/code&gt; indirectly through several dependencies and relied on OS-specific terminal handling, making a clean WASM compilation impractical. The Rust rewrite — pure Rust with no C/FFI, no system calls in the core engine — made WASM a natural target.&lt;/p&gt;
&lt;h2 id=&#34;verification-and-analysis&#34;&gt;Verification and Analysis&lt;/h2&gt;
&lt;p&gt;The screenshot below shows the Workbench analyzing a simplified model of the Signal Protocol. The left panel is the editor with syntax highlighting — keywords like &lt;code&gt;principal&lt;/code&gt; and &lt;code&gt;attacker&lt;/code&gt; in bold, primitives like &lt;code&gt;HASH&lt;/code&gt;, &lt;code&gt;HKDF&lt;/code&gt;, &lt;code&gt;AEAD_ENC&lt;/code&gt; in green, query types in red, and message arrows in amber. The right panel shows the verification results: &lt;code&gt;confidentiality? m1&lt;/code&gt; passes, but &lt;code&gt;authentication? Alice -&amp;gt; Bob: e1&lt;/code&gt; fails — the active attacker can forge the encrypted message.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://symbolic.software/images/blog/verifpal-workbench-analysis.png&#34; alt=&#34;Verifpal Workbench analyzing the Signal protocol&#34;&gt;&lt;/p&gt;
&lt;p&gt;Below the failed query, the Workbench displays the full attack trace — the same output you&amp;rsquo;d see on the command line, showing exactly how the attacker mutates values, which keys they derive, and how they reconstruct the forged ciphertext. This is the Signal model running under a &lt;code&gt;phase[1]&lt;/code&gt; key compromise scenario where both Alice and Bob leak their long-term private keys, so the authentication failure is expected: forward secrecy protects message &lt;em&gt;confidentiality&lt;/em&gt; after key compromise, but not authentication.&lt;/p&gt;
&lt;h2 id=&#34;protocol-diagrams&#34;&gt;Protocol Diagrams&lt;/h2&gt;
&lt;p&gt;Click &amp;ldquo;Diagram&amp;rdquo; to generate an SVG sequence diagram directly from the model text. The diagram below shows the same Signal model visualized as a message sequence chart: Alice and Bob&amp;rsquo;s computation blocks appear as annotation boxes on their lifelines, message arrows show the transmitted constants, and phase boundaries are marked with dashed red lines.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://symbolic.software/images/blog/verifpal-workbench-diagram.png&#34; alt=&#34;Verifpal Workbench protocol diagram for the Signal model&#34;&gt;&lt;/p&gt;
&lt;p&gt;The diagrams are generated entirely client-side by parsing the Verifpal model text in JavaScript — no server round-trip, no external library. They render as inline SVG, so they scale cleanly and can be right-clicked and saved for use in papers or presentations.&lt;/p&gt;
&lt;h2 id=&#34;built-in-examples&#34;&gt;Built-in Examples&lt;/h2&gt;
&lt;p&gt;The dropdown menu includes twelve example models covering a range of protocol patterns:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Simple DH Exchange&lt;/strong&gt; — basic Diffie-Hellman with AEAD&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Challenge-Response&lt;/strong&gt; — nonce-based authentication with signatures&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Signal Protocol&lt;/strong&gt; — simplified X3DH + Double Ratchet with forward secrecy&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Needham-Schroeder&lt;/strong&gt; — classic symmetric-key protocol with known attacks&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Blind Signature&lt;/strong&gt; — BLIND/UNBLIND protocol&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Double Ratchet&lt;/strong&gt; — DH ratchet with symmetric chain keys&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Salt Channel&lt;/strong&gt; — ephemeral DH + signed key exchange&lt;/li&gt;
&lt;li&gt;And five more covering PKE, Shamir secret sharing, digital signatures, and AEAD variants&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each example can be verified in the browser in under a second for simple models, or a few seconds for complex ones like Signal.&lt;/p&gt;
&lt;h2 id=&#34;how-it-works&#34;&gt;How It Works&lt;/h2&gt;
&lt;p&gt;The Workbench is a single HTML page that loads the Verifpal WASM module. The architecture is straightforward:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Rust side.&lt;/strong&gt; The crate exposes two &lt;code&gt;#[wasm_bindgen]&lt;/code&gt; entry points: &lt;code&gt;wasm_verify(input)&lt;/code&gt; and &lt;code&gt;wasm_pretty(input)&lt;/code&gt;. Each call resets all global state (principal names, value IDs, attacker knowledge, analysis counters), parses the model from source text, runs the full verification pipeline, and returns a JSON response with results and captured analysis messages.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-rust&#34; data-lang=&#34;rust&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#[wasm_bindgen]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;fn&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;wasm_verify&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;input&lt;/span&gt;: &lt;span class=&#34;kp&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;str&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;-&amp;gt; &lt;span class=&#34;nb&#34;&gt;String&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;wasm_reset_all_state&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;m&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;match&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;parser&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;parse_string&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;workbench.vp&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;input&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;Ok&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;m&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;m&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;Err&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;e&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;json_error&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;e&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;};&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// ... sanity check, init results, run verification ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;json_results&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;results&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;code&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;messages&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Feature gating.&lt;/strong&gt; The &lt;code&gt;cli&lt;/code&gt; feature (clap, colored, rayon) and the &lt;code&gt;wasm&lt;/code&gt; feature (wasm-bindgen) are mutually exclusive. Rayon parallelism is replaced with sequential execution in the WASM build. The TUI, terminal color output, and OS-dependent code (&lt;code&gt;tput&lt;/code&gt;, &lt;code&gt;open&lt;/code&gt;, &lt;code&gt;thread::sleep&lt;/code&gt;) are compiled out. Analysis progress messages are captured into a buffer instead of printed to stdout.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;JavaScript side.&lt;/strong&gt; The page includes a syntax-highlighted code editor (custom tokenizer — no external library), a results panel showing pass/fail for each query with attack traces, and an SVG protocol diagram generator that parses the model text client-side.&lt;/p&gt;
&lt;h2 id=&#34;try-it&#34;&gt;Try It&lt;/h2&gt;
&lt;p&gt;Open &lt;strong&gt;&lt;a href=&#34;https://verifpal.com/workbench&#34;&gt;verifpal.com/workbench&lt;/a&gt;&lt;/strong&gt;, pick an example from the dropdown, and click Verify. Or write your own model from scratch — the &lt;a href=&#34;https://static.verifpal.com/manual.pdf&#34;&gt;Verifpal User Manual&lt;/a&gt; documents the full language.&lt;/p&gt;
&lt;p&gt;The Workbench is open source as part of the Verifpal project, licensed under GPLv3. The source is available on &lt;a href=&#34;https://github.com/symbolicsoft/verifpal&#34;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
</content:encoded>
      <category>Software</category>
      <category>Research</category>
      <category>Verifpal</category>
      <category>Formal Verification</category>
      
    </item>
    
    <item>
      <title>Verifpal, Rewritten in Rust</title>
      <link>https://symbolic.software/blog/2026-02-23-verifpal-rust/</link>
      <pubDate>Mon, 23 Feb 2026 00:00:00 &#43;0000</pubDate>
      <guid>https://symbolic.software/blog/2026-02-23-verifpal-rust/</guid>
      <description>After seven years in Go, Verifpal has been completely rewritten in Rust, gaining a new analysis engine, massive performance improvements, a rich terminal interface, and a novel attack strategy that finds more attacks.</description>
      <content:encoded>&lt;p&gt;Today we&amp;rsquo;re releasing a complete rewrite of &lt;a href=&#34;https://verifpal.com&#34;&gt;Verifpal&lt;/a&gt; in Rust. Every line of Go has been replaced. The result is a faster, more correct, and more capable protocol verifier — with a new analysis technique, a rich terminal interface, 147 tests (up from 67), and a binary that ships with zero runtime dependencies.&lt;/p&gt;
&lt;p&gt;Verifpal 0.40.1 is an almost complete ground-up rearchitecture that fixes longstanding design limitations while introducing features that weren&amp;rsquo;t possible in the old codebase.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://symbolic.software/images/blog/verifpal-tui.gif&#34; alt=&#34;Verifpal TUI&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;why-rewrite&#34;&gt;Why Rewrite?&lt;/h2&gt;
&lt;p&gt;Verifpal was first released in 2019, written in Go. Go served the project well for its first seven years — fast compilation, easy cross-platform builds, and a straightforward concurrency model. But as the analysis engine grew more sophisticated, Go&amp;rsquo;s limitations started to compound.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Parallel slices instead of structured data.&lt;/strong&gt; The core &lt;code&gt;PrincipalState&lt;/code&gt; type in Go maintained 18 separate parallel slices — &lt;code&gt;Constants&lt;/code&gt;, &lt;code&gt;Assigned&lt;/code&gt;, &lt;code&gt;Creator&lt;/code&gt;, &lt;code&gt;BeforeMutate&lt;/code&gt;, &lt;code&gt;BeforeRewrite&lt;/code&gt;, &lt;code&gt;KnownBy&lt;/code&gt;, &lt;code&gt;DeclaredAt&lt;/code&gt;, &lt;code&gt;Phase&lt;/code&gt;, and more — all correlated by index. Adding a new field meant updating every function that iterated over principal state. An off-by-one in any slice silently corrupted the analysis.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Interface boxing and runtime casts.&lt;/strong&gt; Go&amp;rsquo;s &lt;code&gt;Value&lt;/code&gt; type used an interface (&lt;code&gt;ValueData&lt;/code&gt;) with three implementing types. Every operation required a type switch and cast: &lt;code&gt;v.Data.(*Constant)&lt;/code&gt;. The compiler couldn&amp;rsquo;t help you if you forgot a case or cast to the wrong type. The &lt;code&gt;value.go&lt;/code&gt; file alone was 935 lines, much of it repetitive dispatch logic.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Global mutable state.&lt;/strong&gt; The attacker&amp;rsquo;s knowledge base was a package-level global behind a &lt;code&gt;sync.RWMutex&lt;/code&gt;. Every test run had to reinitialize it. Defensive snapshot copies on every read added allocation pressure in the hottest code paths.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;No unit tests.&lt;/strong&gt; The Go codebase had a single &lt;code&gt;TestMain&lt;/code&gt; function that ran 67 integration tests. There were no unit tests for value equivalence, equation flattening, primitive rewriting, or any of the core symbolic operations. Bugs in these low-level functions only surfaced as incorrect results on full protocol models.&lt;/p&gt;
&lt;p&gt;These weren&amp;rsquo;t insurmountable problems — plenty of good software is written in Go. But for a symbolic verification engine where correctness is paramount and the core data structures are recursive algebraic types, Rust is a natural fit.&lt;/p&gt;
&lt;h2 id=&#34;what-changed&#34;&gt;What Changed&lt;/h2&gt;
&lt;h3 id=&#34;algebraic-types-all-the-way-down&#34;&gt;Algebraic Types All the Way Down&lt;/h3&gt;
&lt;p&gt;The new &lt;code&gt;Value&lt;/code&gt; type is a Rust enum:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-rust&#34; data-lang=&#34;rust&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;enum&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;Value&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Constant&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Constant&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Primitive&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Arc&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Primitive&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Equation&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Arc&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Equation&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Pattern matching is exhaustive — the compiler rejects any function that forgets a variant. Accessor methods like &lt;code&gt;try_as_constant()&lt;/code&gt; return &lt;code&gt;Result&amp;lt;&amp;amp;Constant&amp;gt;&lt;/code&gt; instead of panicking, and errors propagate with &lt;code&gt;?&lt;/code&gt;. The 935-line &lt;code&gt;value.go&lt;/code&gt; has been split into five focused modules: &lt;code&gt;value.rs&lt;/code&gt;, &lt;code&gt;equivalence.rs&lt;/code&gt;, &lt;code&gt;hashing.rs&lt;/code&gt;, &lt;code&gt;resolution.rs&lt;/code&gt;, and &lt;code&gt;rewrite.rs&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&#34;structured-principal-state&#34;&gt;Structured Principal State&lt;/h3&gt;
&lt;p&gt;The 18 parallel slices are gone. Each protocol constant is now a single &lt;code&gt;SlotValues&lt;/code&gt; struct:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-rust&#34; data-lang=&#34;rust&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;SlotValues&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;assigned&lt;/span&gt;: &lt;span class=&#34;nc&#34;&gt;Value&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;before_rewrite&lt;/span&gt;: &lt;span class=&#34;nc&#34;&gt;Value&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;before_mutate&lt;/span&gt;: &lt;span class=&#34;nc&#34;&gt;Value&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;mutated&lt;/span&gt;: &lt;span class=&#34;kt&#34;&gt;bool&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rewritten&lt;/span&gt;: &lt;span class=&#34;kt&#34;&gt;bool&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;creator&lt;/span&gt;: &lt;span class=&#34;nc&#34;&gt;PrincipalId&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sender&lt;/span&gt;: &lt;span class=&#34;nc&#34;&gt;PrincipalId&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Immutable metadata (guard status, wire participation, phase membership) lives in a separate &lt;code&gt;SlotMeta&lt;/code&gt; struct shared via &lt;code&gt;Arc&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-rust&#34; data-lang=&#34;rust&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;PrincipalState&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;meta&lt;/span&gt;: &lt;span class=&#34;nc&#34;&gt;Arc&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;Vec&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;SlotMeta&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;       &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// shared, immutable
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;values&lt;/span&gt;: &lt;span class=&#34;nb&#34;&gt;Vec&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;SlotValues&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// deep-cloned per attack stage
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;index&lt;/span&gt;: &lt;span class=&#34;nc&#34;&gt;Arc&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;HashMap&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ValueId&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;usize&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When cloning state for a new attack stage, only &lt;code&gt;values&lt;/code&gt; is deep-copied. The metadata and index increment an atomic reference count — an O(1) operation. This pattern eliminates an entire class of parallel-slice indexing bugs while reducing clone overhead.&lt;/p&gt;
&lt;h3 id=&#34;three-valued-lifecycle&#34;&gt;Three-Valued Lifecycle&lt;/h3&gt;
&lt;p&gt;Every protocol constant tracks three snapshots of its value: &lt;code&gt;before_mutate&lt;/code&gt; (original), &lt;code&gt;before_rewrite&lt;/code&gt; (after attacker mutation but before primitive rewriting), and &lt;code&gt;assigned&lt;/code&gt; (final). The resolution system uses a simple rule to decide which value a principal actually sees:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-rust&#34; data-lang=&#34;rust&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;fn&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;should_use_before_mutate&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;bp&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;: &lt;span class=&#34;kt&#34;&gt;usize&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;-&amp;gt; &lt;span class=&#34;kt&#34;&gt;bool&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;bp&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;values&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;].&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;creator&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;==&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;bp&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;||&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;bp&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;meta&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;].&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;known&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;||&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;bp&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;meta&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;].&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;wire&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;contains&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;bp&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;||&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;bp&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;values&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;].&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;mutated&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When a principal created a value themselves, or when it wasn&amp;rsquo;t sent over the wire, or when the attacker hasn&amp;rsquo;t mutated it, the principal sees the original. This prevents false-positive authentication failures that plagued earlier versions — a subtle correctness issue that the structured data model makes explicit and verifiable.&lt;/p&gt;
&lt;h3 id=&#34;hand-written-parser&#34;&gt;Hand-Written Parser&lt;/h3&gt;
&lt;p&gt;The Go version used a PEG grammar (&lt;code&gt;libpeg.peg&lt;/code&gt;) that generated parser code. The Rust version replaces it with a hand-written recursive descent parser operating directly on byte slices — zero allocations during lexing, better error messages with position context, and no build-time code generation dependency. The parser is 841 lines, self-contained, and easy to maintain.&lt;/p&gt;
&lt;h3 id=&#34;trait-based-primitive-dispatch&#34;&gt;Trait-Based Primitive Dispatch&lt;/h3&gt;
&lt;p&gt;The 21 built-in cryptographic primitives (AEAD_ENC, AEAD_DEC, SIGN, SIGNVERIF, HASH, HKDF, DH operations, PKE, blind signatures, Shamir secret sharing, and more) are defined as structured specs with function pointer dispatch:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-rust&#34; data-lang=&#34;rust&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;PrimitiveSpec&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;name&lt;/span&gt;: &lt;span class=&#34;kp&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;&amp;#39;static&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;str&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;decompose&lt;/span&gt;: &lt;span class=&#34;nc&#34;&gt;DecomposeRule&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;recompose&lt;/span&gt;: &lt;span class=&#34;nc&#34;&gt;RecomposeRule&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rewrite&lt;/span&gt;: &lt;span class=&#34;nc&#34;&gt;RewriteRule&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rebuild&lt;/span&gt;: &lt;span class=&#34;nc&#34;&gt;RebuildRule&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;definition_check&lt;/span&gt;: &lt;span class=&#34;kt&#34;&gt;bool&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;explosive&lt;/span&gt;: &lt;span class=&#34;kt&#34;&gt;bool&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;pub&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;password_hashing&lt;/span&gt;: &lt;span class=&#34;nb&#34;&gt;Vec&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;usize&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Each rule encodes the primitive&amp;rsquo;s cryptographic semantics declaratively. For example, AEAD_ENC&amp;rsquo;s decompose rule says: &amp;ldquo;given the key (argument 0), reveal the plaintext (argument 1); associated data (argument 2) is always publicly extractable.&amp;rdquo; The Go version encoded the same logic through scattered switch statements across multiple files.&lt;/p&gt;
&lt;p&gt;The rewrite also cleanly separates two concepts that were previously conflated: &lt;strong&gt;definition check&lt;/strong&gt; (an inherent property of the primitive — AEAD_DEC always requires the correct key) and &lt;strong&gt;instance check&lt;/strong&gt; (set by the &lt;code&gt;?&lt;/code&gt; suffix in model syntax — this specific decryption operation is verified by the principal). This distinction is critical for correctly modeling protocols where some cryptographic operations are checked and others are blindly accepted.&lt;/p&gt;
&lt;h3 id=&#34;no-globals-explicit-context&#34;&gt;No Globals, Explicit Context&lt;/h3&gt;
&lt;p&gt;All mutable verification state lives in a &lt;code&gt;VerifyContext&lt;/code&gt; struct passed explicitly through the call stack. The only global mutable state in the entire codebase is a single &lt;code&gt;AtomicU32&lt;/code&gt; analysis counter used by the TUI — a pragmatic exception for a progress indicator that must be readable from a rendering thread without holding a reference to the verification context.&lt;/p&gt;
&lt;p&gt;RwLock poison recovery (&lt;code&gt;unwrap_or_else(|e| e.into_inner())&lt;/code&gt;) prevents cascading failures when a thread panics during testing, ensuring that one failing test doesn&amp;rsquo;t poison the lock for all subsequent tests running in parallel.&lt;/p&gt;
&lt;h2 id=&#34;equation-bypass-a-new-attack-strategy&#34;&gt;Equation Bypass: A New Attack Strategy&lt;/h2&gt;
&lt;p&gt;The most significant new feature isn&amp;rsquo;t about language or architecture — it&amp;rsquo;s a new analysis technique called &lt;strong&gt;Equation Bypass&lt;/strong&gt; that finds attacks the Go version sometimes missed.&lt;/p&gt;
&lt;h3 id=&#34;the-problem&#34;&gt;The Problem&lt;/h3&gt;
&lt;p&gt;Verifpal&amp;rsquo;s active attacker analysis works by systematically replacing wire values with attacker-controlled alternatives, then checking whether the attacker can derive secrets or forge messages. The search is staged: stage 1 tries single-value replacements, stage 2 tries pairs, and so on. For simple protocols, this works well.&lt;/p&gt;
&lt;p&gt;But for protocols like &lt;a href=&#34;https://ssbc.github.io/scuttlebutt-protocol-guide/&#34;&gt;Scuttlebutt&lt;/a&gt;, the brute-force search never terminates. The combinatorial space is too large, and the canonical attack — replacing an ephemeral DH public key with the attacker&amp;rsquo;s own — is buried in a haystack of useless mutations. The old Go verifier would run indefinitely on Scuttlebutt without ever finding the attack that any cryptographer would spot in minutes.&lt;/p&gt;
&lt;h3 id=&#34;the-solution&#34;&gt;The Solution&lt;/h3&gt;
&lt;p&gt;Equation Bypass is a targeted analysis pass that runs before the general mutation loop. It directly models the most common class of active attack: replacing unguarded Diffie-Hellman public keys with the attacker&amp;rsquo;s own public key (G^nil, where nil is the attacker&amp;rsquo;s known private key).&lt;/p&gt;
&lt;p&gt;The mechanism has four phases:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Replace.&lt;/strong&gt; For each principal, collect all equation-valued wire inputs that are received from another principal and not guarded (not sent in &lt;code&gt;[brackets]&lt;/code&gt;). Try replacing each one individually with G^nil, and also try replacing all of them simultaneously.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Identify bypassable guards.&lt;/strong&gt; After replacement, resolve all values and attempt primitive rewrites. Guarded primitives (AEAD_DEC?, SIGNVERIF?) whose rewrite fails are collected. For each failed guard, extract the &amp;ldquo;bypass key&amp;rdquo; — the value the attacker would need to forge a valid input. For AEAD_DEC, that&amp;rsquo;s the decryption key. For SIGNVERIF with a public key G^sk, that&amp;rsquo;s the private key sk. If the attacker can obtain the bypass key — because it&amp;rsquo;s already known, or reconstructible from known values — the guard is bypassable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Inject.&lt;/strong&gt; For each bypassable guard, inject G^nil into the guard&amp;rsquo;s output slot. This injection must update all three state vectors (assigned, before_rewrite, and before_mutate) to ensure correct propagation through downstream computations. The injection iterates up to 5 times to handle cascading guards: bypassing one guard may change downstream keys enough to make a later guard bypassable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Decompose.&lt;/strong&gt; With the fully-resolved bypass state, attempt to decompose all wire-carried primitive values. For example, AEAD_ENC(key, plaintext, nil) where the attacker now knows the key yields the plaintext.&lt;/p&gt;
&lt;h3 id=&#34;the-scuttlebutt-result&#34;&gt;The Scuttlebutt Result&lt;/h3&gt;
&lt;p&gt;For Bob&amp;rsquo;s state in the Scuttlebutt protocol, replacing &lt;code&gt;ephemeralAPub&lt;/code&gt; with G^nil causes a cascade:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Every shared secret Bob derives from Alice&amp;rsquo;s ephemeral key now uses G^nil — which the attacker knows.&lt;/li&gt;
&lt;li&gt;The master secret derived from these shared secrets is reconstructible by the attacker.&lt;/li&gt;
&lt;li&gt;The first AEAD_DEC guard fails, but the attacker knows the key — so G^nil is injected.&lt;/li&gt;
&lt;li&gt;This propagates through the second AEAD_DEC guard, which also becomes bypassable.&lt;/li&gt;
&lt;li&gt;The attacker decomposes both encrypted messages, learning m1 and m2.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;All 9 security queries fail. The attack is found in seconds. The Go version would have run forever.&lt;/p&gt;
&lt;h2 id=&#34;a-rich-terminal-interface&#34;&gt;A Rich Terminal Interface&lt;/h2&gt;
&lt;p&gt;The Rust rewrite introduces a full-screen terminal user interface for live analysis visualization. When running with the &lt;code&gt;--tui&lt;/code&gt; flag, Verifpal renders:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;protocol overview&lt;/strong&gt; showing principals, their private/generated/computed values, and message flows&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Live query status&lt;/strong&gt; with pass/fail indicators updating in real time&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Attacker activity tracking&lt;/strong&gt;: current scan target, mutation weight, budget consumption, and last worthwhile mutations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Knowledge gain feed&lt;/strong&gt;: newly deduced values color-coded by derivation method (decomposition, reconstruction, recomposition, equivalence, password guessing)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Contextual narrative text&lt;/strong&gt; describing the attacker&amp;rsquo;s actions in natural language&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The TUI uses an alternate screen buffer, hides the cursor, detects terminal width, and throttles redraws to 20fps to avoid flooding the terminal. All rendering uses 1-cell-wide Unicode characters (box drawing, block elements) for correct alignment across terminal emulators.&lt;/p&gt;
&lt;h3 id=&#34;character-modes&#34;&gt;Character Modes&lt;/h3&gt;
&lt;p&gt;Because formal verification should be fun: the &lt;code&gt;--character&lt;/code&gt; flag lets you switch the attacker&amp;rsquo;s narrative voice. The default is a clinical security analyst. But you can also run analysis as &lt;strong&gt;Jevil&lt;/strong&gt; or &lt;strong&gt;Spamton&lt;/strong&gt; from Deltarune, complete with character-appropriate commentary on every phase of the attack:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Jevil (on deduction):&lt;/strong&gt; &amp;ldquo;UEE HEE HEE! A SECRET FALLS INTO MY TINY HANDS!&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Spamton (on mutation):&lt;/strong&gt; &amp;ldquo;HEY Alice!! WANT SOME [[Slightly Used]] REPLACEMENT VALUES?? ONLY 3 [[Kromer]]!!&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Spamton (on query failure):&lt;/strong&gt; &amp;ldquo;[[DEAL OF A LIFETIME]]!! YOUR PROTOCOL IS [[Bankrupt]]!!&amp;rdquo;&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;expanded-test-suite&#34;&gt;Expanded Test Suite&lt;/h2&gt;
&lt;p&gt;The Go version had 67 integration tests and no unit tests. The Rust version has &lt;strong&gt;147 tests&lt;/strong&gt;: 70 unit tests covering value equivalence, equation flattening, primitive handling, and other core operations, plus 77 integration tests running full protocol models.&lt;/p&gt;
&lt;p&gt;Fifteen new protocol models were added, covering scenarios the old test suite never touched:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Double Ratchet&lt;/strong&gt; (&lt;code&gt;double_ratchet.vp&lt;/code&gt;): Signal-style DH ratcheting with alternating rounds and HKDF chain key derivation&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Triple DH&lt;/strong&gt; (&lt;code&gt;triple_dh.vp&lt;/code&gt;): X3DH-style key agreement combining identity, signed, and ephemeral DH computations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Four-Party Relay&lt;/strong&gt; (&lt;code&gt;four_party.vp&lt;/code&gt;): Multi-hop message relay with cross-principal authentication and re-encryption&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Phase Forward Secrecy&lt;/strong&gt; (&lt;code&gt;phase_forward_secrecy.vp&lt;/code&gt;): Ephemeral DH ensuring confidentiality survives long-term key compromise&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PSK + DH&lt;/strong&gt; (&lt;code&gt;psk_with_dh.vp&lt;/code&gt;): TLS PSK+DHE-style hybrid key agreement with forward secrecy under PSK compromise&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Key Ratchet&lt;/strong&gt; (&lt;code&gt;key_ratchet.vp&lt;/code&gt;): Symmetric HKDF chain key ratcheting across multiple messages&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Shamir Reconstruction&lt;/strong&gt; (&lt;code&gt;shamir_reconstruction.vp&lt;/code&gt;): 2-of-3 threshold secret sharing with partial share leakage&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Blind Signature&lt;/strong&gt; (&lt;code&gt;blind_signature.vp&lt;/code&gt;): Message blinding, blind signing, and signature unblinding&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deep Nesting&lt;/strong&gt; (&lt;code&gt;deep_nesting.vp&lt;/code&gt;): Five layers of nested encryption testing value resolution depth&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Concat Bomb&lt;/strong&gt; variants: Maximum-arity concatenation testing decomposition, equivalence, key leakage, and unchecked assertions&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Many Principals&lt;/strong&gt; (&lt;code&gt;many_principals.vp&lt;/code&gt;): Broadcast signature verification across six recipients&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Passive DH Chain&lt;/strong&gt; (&lt;code&gt;passive_dh_chain.vp&lt;/code&gt;): Three-hop relay testing chain-of-custody under passive attackers&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These models test real-world protocol patterns — ratcheting, multi-party flows, threshold cryptography, hybrid key agreement — that represent the bread and butter of modern protocol design.&lt;/p&gt;
&lt;h2 id=&#34;by-the-numbers&#34;&gt;By the Numbers&lt;/h2&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;&lt;/th&gt;
          &lt;th&gt;Go (v0.31.2)&lt;/th&gt;
          &lt;th&gt;Rust&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;strong&gt;Language&lt;/strong&gt;&lt;/td&gt;
          &lt;td&gt;Go 1.22&lt;/td&gt;
          &lt;td&gt;Rust (2024 edition)&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;strong&gt;Source files&lt;/strong&gt;&lt;/td&gt;
          &lt;td&gt;22&lt;/td&gt;
          &lt;td&gt;26&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;strong&gt;Lines of code&lt;/strong&gt;&lt;/td&gt;
          &lt;td&gt;~7,100&lt;/td&gt;
          &lt;td&gt;~9,600&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;strong&gt;Dependencies&lt;/strong&gt;&lt;/td&gt;
          &lt;td&gt;14 (go.sum: 562 lines)&lt;/td&gt;
          &lt;td&gt;3 (colored, clap, rayon)&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;strong&gt;Test count&lt;/strong&gt;&lt;/td&gt;
          &lt;td&gt;67 integration&lt;/td&gt;
          &lt;td&gt;70 unit + 77 integration&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;strong&gt;Parser&lt;/strong&gt;&lt;/td&gt;
          &lt;td&gt;Generated PEG&lt;/td&gt;
          &lt;td&gt;Hand-written recursive descent&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;strong&gt;Largest file&lt;/strong&gt;&lt;/td&gt;
          &lt;td&gt;value.go (935 lines)&lt;/td&gt;
          &lt;td&gt;tui.rs (TUI, new feature)&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;strong&gt;Global mutable state&lt;/strong&gt;&lt;/td&gt;
          &lt;td&gt;AttackerState + mutex&lt;/td&gt;
          &lt;td&gt;1 AtomicU32 (TUI counter)&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;strong&gt;Build&lt;/strong&gt;&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;go build&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;cargo build --release&lt;/code&gt; (LTO, single codegen unit)&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The Rust binary ships with fat link-time optimization, a single codegen unit, symbol stripping, and panic=abort — producing a compact, fully static binary with no runtime dependencies.&lt;/p&gt;
&lt;h2 id=&#34;getting-verifpal&#34;&gt;Getting Verifpal&lt;/h2&gt;
&lt;p&gt;Verifpal is available now:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Homebrew&lt;/strong&gt; (Linux/macOS): &lt;code&gt;brew install verifpal&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scoop&lt;/strong&gt; (Windows): &lt;code&gt;scoop install verifpal&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Source&lt;/strong&gt;: &lt;code&gt;cargo build --release&lt;/code&gt; from the &lt;a href=&#34;https://github.com/symbolicsoft/verifpal&#34;&gt;GitHub repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Releases&lt;/strong&gt;: Pre-built binaries for Windows, Linux, macOS, and FreeBSD on &lt;a href=&#34;https://github.com/symbolicsoft/verifpal/releases&#34;&gt;GitHub Releases&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;a href=&#34;https://static.verifpal.com/manual.pdf&#34;&gt;Verifpal User Manual&lt;/a&gt; and the &lt;a href=&#34;https://github.com/symbolicsoft/verifpal-vscode&#34;&gt;VS Code extension&lt;/a&gt; (syntax highlighting, live analysis, diagram visualizations) remain fully compatible with the Rust version.&lt;/p&gt;
&lt;p&gt;This rewrite represents weeks of careful work. Every test passes. Every protocol model produces identical results to the Go version (plus the new ones the Go version couldn&amp;rsquo;t handle). The codebase is smaller in spirit if not in line count — structured, typed, and tested in ways that weren&amp;rsquo;t possible before.&lt;/p&gt;
</content:encoded>
      <category>Software</category>
      <category>Verifpal</category>
      <category>Formal Verification</category>
      
    </item>
    
    <item>
      <title>Security Announcement: Bugs in Noise Explorer&#39;s Rust/WASM Code Generation</title>
      <link>https://symbolic.software/blog/2026-02-22-noiseexplorer-advisory/</link>
      <pubDate>Sun, 22 Feb 2026 00:00:00 &#43;0000</pubDate>
      <guid>https://symbolic.software/blog/2026-02-22-noiseexplorer-advisory/</guid>
      <description>We&#39;ve updated Noise Explorer to address two bugs in generated Rust and WebAssembly implementations of Noise Protocol Framework handshake patterns.</description>
      <content:encoded>&lt;p&gt;&lt;strong&gt;Update (February 23, 2026):&lt;/strong&gt; A second security advisory has been published: &lt;a href=&#34;https://github.com/symbolicsoft/noiseexplorer/security/advisories/GHSA-q6mw-qh5x-m2p8&#34;&gt;GHSA-q6mw-qh5x-m2p8&lt;/a&gt;. This advisory addresses four additional bugs across Go, Rust and WebAssembly code generation templates, including a truncated forbidden Curve25519 point value in Go, a panic-on-validation crash reachable by remote attackers in Go, a fixed-size buffer overflow enabling denial of service in Rust and WebAssembly, and a public key validation bypass in Rust and WebAssembly key generation. These issues are fixed in &lt;a href=&#34;https://github.com/symbolicsoft/noiseexplorer/releases/tag/v1.0.6&#34;&gt;Noise Explorer v1.0.6&lt;/a&gt;. &lt;strong&gt;Users who have generated Go, Rust or WebAssembly implementations should regenerate their code using version 1.0.6 or later.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://noiseexplorer.com&#34;&gt;Noise Explorer&lt;/a&gt; is Symbolic Software&amp;rsquo;s online engine for reasoning about &lt;a href=&#34;https://noiseprotocol.org&#34;&gt;Noise Protocol Framework&lt;/a&gt; Handshake Patterns. It allows users to design Noise Handshake Patterns, generate formal verification models, explore pre-computed formal verification results, and generate secure software implementations in Go, Rust and WebAssembly.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ve updated Noise Explorer to address two bugs in its Rust and WebAssembly code generation templates. These bugs affected all Noise protocol implementations generated by Noise Explorer versions 1.0.4 and earlier. &lt;strong&gt;Users who have generated Rust or WebAssembly implementations using Noise Explorer should regenerate their code using &lt;a href=&#34;https://github.com/symbolicsoft/noiseexplorer/releases/tag/v1.0.5&#34;&gt;version 1.0.5&lt;/a&gt; or later.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A security advisory has been published: &lt;a href=&#34;https://github.com/symbolicsoft/noiseexplorer/security/advisories/GHSA-6pc6-w328-gw8x&#34;&gt;GHSA-6pc6-w328-gw8x&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;bug-1-public-key-validation-bypass-rust-only&#34;&gt;Bug 1: Public Key Validation Bypass (Rust Only)&lt;/h2&gt;
&lt;p&gt;The first bug was located in the Rust code generation template (&lt;code&gt;src/rs/1types.rs&lt;/code&gt;). The &lt;code&gt;PublicKey::from_str()&lt;/code&gt; function bypassed small-order Curve25519 point validation, allowing an attacker to supply a low-order public key. A Diffie-Hellman operation with a small-order point produces a predictable all-zero shared secret, which could lead to a loss of message confidentiality.&lt;/p&gt;
&lt;p&gt;This bug affected generated Rust implementations only. Go and WebAssembly implementations were not affected.&lt;/p&gt;
&lt;h2 id=&#34;bug-2-incorrect-cipherstate-rekeying-rust-and-wasm&#34;&gt;Bug 2: Incorrect Cipherstate Rekeying (Rust and WASM)&lt;/h2&gt;
&lt;p&gt;The second bug was located in the Rust and WebAssembly code generation templates (&lt;code&gt;src/rs/6processes.rs&lt;/code&gt; and &lt;code&gt;src/wasm/6processes.rs&lt;/code&gt;). The &lt;code&gt;rekey_remote_cipherstate&lt;/code&gt; function incorrectly operated on the local cipherstate instead of the remote one. This caused desynchronization between peers: after a rekey operation, subsequent sent messages would use a doubly-rekeyed key, causing decryption failures on the receiving side.&lt;/p&gt;
&lt;p&gt;This bug affected both generated Rust and WebAssembly implementations. Go implementations were not affected.&lt;/p&gt;
&lt;h2 id=&#34;unaffected-components&#34;&gt;Unaffected Components&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;All Go implementations generated by Noise Explorer.&lt;/li&gt;
&lt;li&gt;All ProVerif formal verification models generated by Noise Explorer.&lt;/li&gt;
&lt;li&gt;WebAssembly implementations (for the public key validation bug only).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;impact&#34;&gt;Impact&lt;/h2&gt;
&lt;p&gt;The public key validation bypass could allow an attacker to force a predictable shared secret, undermining the confidentiality of messages encrypted under the resulting session keys. The cipherstate rekeying bug would cause communication failures between peers after a rekey operation. Neither bug affected the correctness of Noise Explorer&amp;rsquo;s formal verification models.&lt;/p&gt;
&lt;h2 id=&#34;resolution&#34;&gt;Resolution&lt;/h2&gt;
&lt;p&gt;Both bugs have been fixed in &lt;a href=&#34;https://github.com/symbolicsoft/noiseexplorer/releases/tag/v1.0.5&#34;&gt;Noise Explorer v1.0.5&lt;/a&gt;. The Rust code generation templates have also been upgraded to the Rust 2024 edition with updated dependencies.&lt;/p&gt;
&lt;p&gt;Users who have previously generated Rust or WebAssembly Noise protocol implementations using Noise Explorer should regenerate their implementations using version 1.0.5 or later.&lt;/p&gt;
&lt;h2 id=&#34;acknowledgements&#34;&gt;Acknowledgements&lt;/h2&gt;
&lt;p&gt;We thank Elichai Turkel for identifying both issues through code review of a generated Noise-KK Rust implementation.&lt;/p&gt;
</content:encoded>
      <category>Security</category>
      <category>Noise Explorer</category>
      <category>Protocol Design</category>
      
    </item>
    
    <item>
      <title>Even More Bugs in Cryspen&#39;s libcrux: ML-DSA</title>
      <link>https://symbolic.software/blog/2026-02-17-cryspen-mldsa/</link>
      <pubDate>Tue, 17 Feb 2026 00:00:00 &#43;0000</pubDate>
      <guid>https://symbolic.software/blog/2026-02-17-cryspen-mldsa/</guid>
      <description>Three findings in libcrux&#39;s ML-DSA implementation: a verifier norm check that is dead code due to a wrong constant, a missing bounds check in hint deserialization, and a wrong multiplication specification that renders AVX2 proofs unsound.</description>
      <content:encoded>




&lt;nav class=&#34;series-nav&#34; aria-label=&#34;Cryspen series (top)&#34;&gt;
  &lt;header class=&#34;series-nav-header&#34;&gt;
    &lt;span class=&#34;series-nav-label&#34;&gt;▶ Cryspen Series&lt;/span&gt;
    &lt;span class=&#34;series-nav-count&#34;&gt;5 posts · 2 papers · 1 talk&lt;/span&gt;
  &lt;/header&gt;
  &lt;ol class=&#34;series-nav-list&#34;&gt;
    
    
    &lt;li&gt;
      
      &lt;a href=&#34;https://symbolic.software/blog/2026-02-05-cryspen/&#34;&gt;On the Promises of &amp;#39;High-Assurance&amp;#39; Cryptography&lt;/a&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.02.05&lt;/span&gt;
    &lt;/li&gt;
    
    
    &lt;li&gt;
      
      &lt;a href=&#34;https://symbolic.software/blog/2026-02-12-cryspen-response/&#34;&gt;We Found Bugs in Cryspen&amp;#39;s Verified Code&lt;/a&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.02.12&lt;/span&gt;
    &lt;/li&gt;
    
    
    &lt;li class=&#34;current&#34; aria-current=&#34;page&#34;&gt;
      
      &lt;span class=&#34;title&#34;&gt;Even More Bugs in Cryspen&amp;#39;s libcrux: ML-DSA&lt;/span&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.02.17&lt;/span&gt;
    &lt;/li&gt;
    
    
    &lt;li&gt;
      
      &lt;a href=&#34;https://symbolic.software/blog/2026-03-07-cryspen-tls/&#34;&gt;Cryspen&amp;#39;s Approach to TLS: A Critical Analysis&lt;/a&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.03.07&lt;/span&gt;
    &lt;/li&gt;
    
    
    &lt;li&gt;
      
      &lt;a href=&#34;https://symbolic.software/blog/2026-04-07-cryspen-hax/&#34;&gt;The Verification Facade: Structural Gaps in Hax&lt;/a&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.04.07&lt;/span&gt;
    &lt;/li&gt;
    
  &lt;/ol&gt;
  &lt;div class=&#34;series-nav-footer&#34;&gt;
    &lt;div class=&#34;series-nav-row&#34;&gt;
      &lt;span class=&#34;series-nav-rowlabel&#34;&gt;Papers&lt;/span&gt;
      &lt;a href=&#34;https://eprint.iacr.org/2026/192&#34;&gt;&lt;em&gt;Verification Theatre&lt;/em&gt;&lt;/a&gt;
      &lt;span class=&#34;series-nav-sep&#34;&gt;·&lt;/span&gt;
      &lt;a href=&#34;https://eprint.iacr.org/2026/670&#34;&gt;&lt;em&gt;Verification Facade&lt;/em&gt;&lt;/a&gt;
    &lt;/div&gt;
    &lt;div class=&#34;series-nav-row&#34;&gt;
      &lt;span class=&#34;series-nav-rowlabel&#34;&gt;Talk&lt;/span&gt;
      &lt;a href=&#34;https://www.youtube.com/watch?v=TdOXza1-M_4&#34;&gt;&lt;em&gt;High Assurance Cryptography and the Ethics of Disclosure (OSTIF)&lt;/em&gt;&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/nav&gt;

&lt;p&gt;Over the past two weeks, we have published &lt;a href=&#34;https://symbolic.software/blog/2026-02-05-cryspen/&#34;&gt;eight vulnerabilities&lt;/a&gt; across Cryspen&amp;rsquo;s cryptographic libraries, including &lt;a href=&#34;https://symbolic.software/blog/2026-02-12-cryspen-response/&#34;&gt;three bugs in formally verified code&lt;/a&gt; that Cryspen&amp;rsquo;s own documentation claims is correct.&lt;/p&gt;
&lt;p&gt;We now report three additional findings in libcrux&amp;rsquo;s ML-DSA implementation&amp;mdash;the post-quantum digital signature algorithm standardized as FIPS 204. The first two are FIPS 204 specification violations in runtime code. The third is a wrong specification in the F* formal verification infrastructure that renders AVX2 proofs unsound.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update (Feb 19):&lt;/strong&gt; Finding 3 added&amp;mdash;an error in the ML-DSA AVX2 proof specification that makes the formally verified intrinsics axioms logically false.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update (Mar 6):&lt;/strong&gt; Cryspen has fixed Findings 1 and 2. Finding 1 (verifier norm check) was fixed in &lt;a href=&#34;https://github.com/cryspen/libcrux/pull/1347&#34;&gt;PR #1347&lt;/a&gt;, merged March 4, 2026. Finding 2 (hint deserialization) was fixed in &lt;a href=&#34;https://github.com/cryspen/libcrux/pull/1348&#34;&gt;PR #1348&lt;/a&gt;, merged March 4, 2026. Both PRs credit the discoverer.&lt;/p&gt;
&lt;h2 id=&#34;finding-1-ml-dsa-verifier-norm-check-is-dead-code&#34;&gt;Finding 1: ML-DSA Verifier Norm Check Is Dead Code&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Location:&lt;/strong&gt; &lt;code&gt;libcrux-ml-dsa/src/ml_dsa_generic.rs&lt;/code&gt;, line 404.&lt;/p&gt;
&lt;p&gt;The ML-DSA verifier checks whether the signer response vector $\mathbf{z}$ has an infinity norm below a certain bound. This is a critical security check: it ensures that $\mathbf{z}$ does not leak information about the secret key. FIPS 204, Algorithm 8 (ML-DSA.Verify_internal), step 13 specifies:&lt;/p&gt;
$$\text{return } \lVert\mathbf{z}\rVert_\infty &lt; \gamma_1 - \beta \text{ and } \tilde{c} = \tilde{c}&#39;$$&lt;p&gt;And Algorithm 7 (ML-DSA.Sign_internal), step 23 specifies:&lt;/p&gt;
$$\text{if } \lVert\mathbf{z}\rVert_\infty \geq \gamma_1 - \beta \text{ or } \lVert r_0\rVert_\infty \geq \gamma_2 - \beta \text{, then } (\mathbf{z}, \mathbf{h}) \leftarrow \bot$$&lt;p&gt;Both use $\gamma_1 - \beta$. The libcrux verifier uses the wrong bound:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-rust&#34; data-lang=&#34;rust&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// Line 402-405 (verifier, BUG):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;vector_infinity_norm_exceeds&lt;/span&gt;::&lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;SIMDUnit&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;deserialized_signer_response&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;no&#34;&gt;GAMMA1_EXPONENT&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;no&#34;&gt;BETA&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;//   ^^^^^^^^^^^^^^^^^^^^^^^^ = 2·γ₁ - β
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;Err&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;VerificationError&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;SignerResponseExceedsBoundError&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The expression &lt;code&gt;2 &amp;lt;&amp;lt; GAMMA1_EXPONENT&lt;/code&gt; computes $2 \cdot 2^{\gamma_1\text{exp}} = 2\gamma_1$, not $\gamma_1$. Compare with the signer, which correctly uses &lt;code&gt;1 &amp;lt;&amp;lt; GAMMA1_EXPONENT&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-rust&#34; data-lang=&#34;rust&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// Line 284 (signer, correct):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;vector_infinity_norm_exceeds&lt;/span&gt;::&lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;SIMDUnit&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;mask&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;no&#34;&gt;GAMMA1_EXPONENT&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;no&#34;&gt;BETA&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;//                                                  ^^^^^^^^^^^^^^^^^^^^^^^^ = γ₁ - β
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The signer checks $\gamma_1 - \beta$. The verifier checks $2\gamma_1 - \beta$. One constant was doubled.&lt;/p&gt;
&lt;h3 id=&#34;the-check-can-never-trigger&#34;&gt;The Check Can Never Trigger&lt;/h3&gt;
&lt;p&gt;The signer response $\mathbf{z}$ is deserialized from a $\gamma_1$-encoded representation: each coefficient is encoded as a value in $[-\gamma_1, \gamma_1]$. This means that for any deserialized $\mathbf{z}$, $\lVert\mathbf{z}\rVert_\infty \leq \gamma_1$. The buggy bound is $2\gamma_1 - \beta$. Since $\beta &gt; 0$:&lt;/p&gt;
$$\gamma_1 &lt; 2\gamma_1 - \beta$$&lt;p&gt;The check &lt;code&gt;vector_infinity_norm_exceeds(&amp;amp;deserialized_signer_response, (2 &amp;lt;&amp;lt; GAMMA1_EXPONENT) - BETA)&lt;/code&gt; can never return true. The verifier&amp;rsquo;s norm check is dead code. For each ML-DSA parameter set:&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Parameter Set&lt;/th&gt;
          &lt;th&gt;$\gamma_1$&lt;/th&gt;
          &lt;th&gt;$\beta$&lt;/th&gt;
          &lt;th&gt;Max $\lVert\mathbf{z}\rVert_\infty$&lt;/th&gt;
          &lt;th&gt;Buggy bound ($2\gamma_1 - \beta$)&lt;/th&gt;
          &lt;th&gt;Correct bound ($\gamma_1 - \beta$)&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;ML-DSA-44&lt;/td&gt;
          &lt;td&gt;$2^{17} = 131072$&lt;/td&gt;
          &lt;td&gt;78&lt;/td&gt;
          &lt;td&gt;131072&lt;/td&gt;
          &lt;td&gt;262066&lt;/td&gt;
          &lt;td&gt;130994&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;ML-DSA-65&lt;/td&gt;
          &lt;td&gt;$2^{19} = 524288$&lt;/td&gt;
          &lt;td&gt;196&lt;/td&gt;
          &lt;td&gt;524288&lt;/td&gt;
          &lt;td&gt;1048380&lt;/td&gt;
          &lt;td&gt;524092&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;ML-DSA-87&lt;/td&gt;
          &lt;td&gt;$2^{19} = 524288$&lt;/td&gt;
          &lt;td&gt;120&lt;/td&gt;
          &lt;td&gt;524288&lt;/td&gt;
          &lt;td&gt;1048456&lt;/td&gt;
          &lt;td&gt;524168&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;In every case, the maximum possible infinity norm ($\gamma_1$) is less than the buggy bound ($2\gamma_1 - \beta$). The check is vacuously true. The verifier unconditionally passes any deserialized signer response, regardless of whether it satisfies the FIPS 204 requirement $\lVert\mathbf{z}\rVert_\infty &lt; \gamma_1 - \beta$.&lt;/p&gt;
&lt;h3 id=&#34;concrete-impact&#34;&gt;Concrete Impact&lt;/h3&gt;
&lt;p&gt;A conforming signer will never produce a signature with $\lVert\mathbf{z}\rVert_\infty \geq \gamma_1 - \beta$&amp;mdash;the signing algorithm rejects such candidates and retries. But a malicious signer could produce signatures with $\gamma_1 - \beta \leq \lVert\mathbf{z}\rVert_\infty \leq \gamma_1$ that pass libcrux&amp;rsquo;s verifier but would be correctly rejected by any FIPS 204-conforming implementation.&lt;/p&gt;
&lt;p&gt;This creates an interoperability hazard. A system using libcrux for verification would accept signatures that every other conforming implementation rejects:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Post-quantum certificate chains.&lt;/strong&gt; A certificate authority or TLS implementation using libcrux to verify ML-DSA signatures on certificates would accept certificates that conforming verifiers reject. An attacker could issue a certificate that appears valid to libcrux-based systems but is rejected everywhere else, creating a split view of the PKI.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Firmware signing.&lt;/strong&gt; A secure boot chain using libcrux for signature verification would accept firmware images signed with out-of-bound $\mathbf{z}$ vectors. A malicious firmware image could pass libcrux&amp;rsquo;s verification while being correctly rejected by any other FIPS 204-conforming verifier on the same device or in audit tooling.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consensus protocols.&lt;/strong&gt; In a distributed system where nodes verify ML-DSA signatures to reach agreement, libcrux nodes would accept messages that non-libcrux nodes reject. An adversary could exploit this to partition the network: libcrux nodes see a valid message, conforming nodes do not.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;provenance&#34;&gt;Provenance&lt;/h3&gt;
&lt;p&gt;Introduced in commit &lt;a href=&#34;https://github.com/cryspen/libcrux/commit/326c837a33&#34;&gt;&lt;code&gt;326c837a33&lt;/code&gt;&lt;/a&gt; on January 7, 2025, by Jonas Schneider-Bensch, with the message &amp;ldquo;Further macro monomorphization of parameter sets.&amp;rdquo; The signer&amp;rsquo;s correct bound was already present in the codebase. The verifier&amp;rsquo;s bound was written incorrectly in the same refactoring pass.&lt;/p&gt;
&lt;h2 id=&#34;finding-2-hint-deserialization-missing-final-bound-check&#34;&gt;Finding 2: Hint Deserialization Missing Final Bound Check&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Location:&lt;/strong&gt; &lt;code&gt;libcrux-ml-dsa/src/encoding/signature.rs&lt;/code&gt;, lines 91&amp;ndash;120.&lt;/p&gt;
&lt;p&gt;ML-DSA signatures contain a hint vector $\mathbf{h}$ that encodes which coefficients of the high-order bits need adjustment. The hint is serialized as a list of indices followed by cumulative counts, with a maximum of $\omega$ total true hints across all polynomials. FIPS 204, Algorithm 21 (HintBitUnpack), step 4 specifies, for every row $i$ from 0 to $k - 1$:&lt;/p&gt;
$$\text{if } y[\omega + i] &lt; \text{Index} \text{ or } y[\omega + i] &gt; \omega \text{ then return } \bot$$&lt;p&gt;This checks the &lt;em&gt;current&lt;/em&gt; cumulative count $y[\omega + i]$ against $\omega$ on &lt;em&gt;every&lt;/em&gt; iteration. It ensures that no row&amp;rsquo;s cumulative count exceeds the allowed maximum.&lt;/p&gt;
&lt;p&gt;The libcrux implementation checks the wrong variable:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-rust&#34; data-lang=&#34;rust&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;in&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;..&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rows_in_a&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;current_true_hints_seen&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;hint_serialized&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;max_ones_in_hint&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;+&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;as&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;usize&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;current_true_hints_seen&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;previous_true_hints_seen&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;||&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;previous_true_hints_seen&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;max_ones_in_hint&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;//      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;//      Checks PREVIOUS iteration&amp;#39;s count, not current.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;//      FIPS 204 checks: y[ω + i] &amp;gt; ω (i.e., current &amp;gt; max)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;malformed_hint&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;break&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;j&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;in&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;previous_true_hints_seen&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;..&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;current_true_hints_seen&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// ... process hint indices ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;set_hint&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;out_hint&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;hint_serialized&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;j&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;as&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;usize&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;previous_true_hints_seen&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;current_true_hints_seen&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The check uses &lt;code&gt;previous_true_hints_seen &amp;gt; max_ones_in_hint&lt;/code&gt; instead of &lt;code&gt;current_true_hints_seen &amp;gt; max_ones_in_hint&lt;/code&gt;. It validates the count from the &lt;em&gt;previous&lt;/em&gt; iteration rather than the current one. For the last row (&lt;code&gt;i = rows_in_a - 1&lt;/code&gt;), the current count is read, the hint indices are processed and applied via &lt;code&gt;set_hint&lt;/code&gt;, and &lt;code&gt;previous_true_hints_seen&lt;/code&gt; is updated&amp;mdash;but the overflow check will not fire until the next iteration, which never comes.&lt;/p&gt;
&lt;p&gt;The subsequent padding check (line 122) is supposed to verify that all remaining bytes in the hint section are zero:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-rust&#34; data-lang=&#34;rust&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;j&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;in&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;previous_true_hints_seen&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;..&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;max_ones_in_hint&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;hint_serialized&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;j&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;malformed_hint&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;break&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But if &lt;code&gt;previous_true_hints_seen&lt;/code&gt; equals or exceeds &lt;code&gt;max_ones_in_hint&lt;/code&gt; after the last row, this range is empty and the loop body never executes. The padding check is silently skipped.&lt;/p&gt;
&lt;h3 id=&#34;concrete-impact-1&#34;&gt;Concrete Impact&lt;/h3&gt;
&lt;p&gt;A crafted signature can set the last row&amp;rsquo;s cumulative count to a value greater than $\omega$ (&lt;code&gt;max_ones_in_hint&lt;/code&gt;). The inner loop will then iterate over bytes in the trailer region of the hint encoding, interpreting arbitrary byte values as hint indices and setting corresponding positions via &lt;code&gt;set_hint&lt;/code&gt;. For each parameter set:&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Parameter Set&lt;/th&gt;
          &lt;th&gt;$\omega$ (&lt;code&gt;max_ones_in_hint&lt;/code&gt;)&lt;/th&gt;
          &lt;th&gt;Rows ($k$)&lt;/th&gt;
          &lt;th&gt;Trailer bytes&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;ML-DSA-44&lt;/td&gt;
          &lt;td&gt;80&lt;/td&gt;
          &lt;td&gt;4&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;hint_serialized[80..84]&lt;/code&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;ML-DSA-65&lt;/td&gt;
          &lt;td&gt;55&lt;/td&gt;
          &lt;td&gt;6&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;hint_serialized[55..61]&lt;/code&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;ML-DSA-87&lt;/td&gt;
          &lt;td&gt;75&lt;/td&gt;
          &lt;td&gt;8&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;hint_serialized[75..83]&lt;/code&gt;&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The trailer bytes are the cumulative count bytes for each row. An attacker controls these values as part of the signature. By setting the last row&amp;rsquo;s count to &lt;code&gt;max_ones_in_hint + rows_in_a&lt;/code&gt; (or any value up to 255), the inner loop reads the trailer bytes as hint indices, setting arbitrary hint positions in the last polynomial. This produces a hint vector that a FIPS 204-conforming implementation would reject.&lt;/p&gt;
&lt;p&gt;As with the verifier norm bound, this creates a divergence between libcrux and conforming implementations. A signature accepted by libcrux may be rejected elsewhere, or worse, the corrupted hint vector may cause the verification to produce a different result than intended:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Signature forgery assistance.&lt;/strong&gt; The hint vector directly affects which high-order bits are adjusted during verification. By injecting arbitrary hint positions through the overflow, an attacker gains additional degrees of freedom in crafting signatures that pass verification. This weakens the effective security of the scheme by relaxing constraints that the signer is supposed to satisfy.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cross-implementation disagreement.&lt;/strong&gt; A signature with an overflowed hint count would verify successfully under libcrux but fail under any conforming implementation. In a system where multiple parties independently verify the same signature&amp;mdash;such as a transparency log or a multi-verifier protocol&amp;mdash;this produces contradictory verification results with no indication of which is correct.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Strong unforgeability violation.&lt;/strong&gt; FIPS 204 mandates a unique hint encoding precisely to ensure strong unforgeability: each valid signature has exactly one valid serialization. The missing bound check allows multiple distinct serializations to decode to the same logical signature, breaking this property. An attacker can produce a second valid encoding of an existing signature without access to the signing key.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;provenance-1&#34;&gt;Provenance&lt;/h3&gt;
&lt;p&gt;The hint deserialization logic has been touched by multiple authors across several commits. The current loop structure was established by Jonas Schneider-Bensch in commit &lt;a href=&#34;https://github.com/cryspen/libcrux/commit/83ffae727d&#34;&gt;&lt;code&gt;83ffae727d&lt;/code&gt;&lt;/a&gt; on January 2, 2025, and further modified by Franziskus Kiefer (Cryspen&amp;rsquo;s CEO) in commit &lt;a href=&#34;https://github.com/cryspen/libcrux/commit/404115bc08&#34;&gt;&lt;code&gt;404115bc08&lt;/code&gt;&lt;/a&gt; on January 13, 2025. The early-return logic was later replaced with flag-and-break by Jonas Schneider-Bensch in commit &lt;a href=&#34;https://github.com/cryspen/libcrux/commit/d979b001f9&#34;&gt;&lt;code&gt;d979b001f9&lt;/code&gt;&lt;/a&gt; on November 17, 2025, as a workaround for a bug in Eurydice, with the comment: &amp;ldquo;We would like to use early returns below, but doing so triggers a bug in Eurydice.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;The workaround for Eurydice&amp;rsquo;s inability to handle early returns inside loops restructured the control flow in a way that obscured the missing final bound check. The original early-return code may or may not have had the same bug, but the flag-and-break rewrite made it harder to spot.&lt;/p&gt;
&lt;h2 id=&#34;finding-3-wrong-multiplication-specification-in-ml-dsa-avx2-proofs&#34;&gt;Finding 3: Wrong Multiplication Specification in ML-DSA AVX2 Proofs&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Location:&lt;/strong&gt; &lt;code&gt;libcrux-ml-dsa/proofs/fstar/spec/Spec.Intrinsics.fsti&lt;/code&gt;, line 97.&lt;/p&gt;
&lt;p&gt;The F* specification file &lt;code&gt;Spec.Intrinsics.fsti&lt;/code&gt; defines the semantics of low-level AVX2 intrinsics used in the ML-DSA proof infrastructure. The function &lt;code&gt;i16_mul_32extended&lt;/code&gt; specifies 16-to-32-bit widening multiplication&amp;mdash;the primitive underlying Intel&amp;rsquo;s &lt;code&gt;VPMADDWD&lt;/code&gt; and &lt;code&gt;VPMULLW&lt;/code&gt; instructions. It takes two parameters &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;[@@ &amp;#34;opaque_to_smt&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;let i16_mul_32extended (x y: i16): i32 =
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  (cast x &amp;lt;: i32) *! (cast x &amp;lt;: i32)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;(*               ^^^ should be: cast y *)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The body uses &lt;code&gt;x&lt;/code&gt; for both operands. The parameter &lt;code&gt;y&lt;/code&gt; is completely ignored. The function computes $x^2$ instead of $x \cdot y$. This is likely a copy-paste error: &lt;code&gt;cast x&lt;/code&gt; was duplicated instead of writing &lt;code&gt;cast y&lt;/code&gt; for the second operand.&lt;/p&gt;
&lt;h3 id=&#34;why-formal-verification-cannot-catch-this&#34;&gt;Why Formal Verification Cannot Catch This&lt;/h3&gt;
&lt;p&gt;Three mechanisms conspire to make this bug invisible to the F* proof system:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. The definition is hidden from the solver.&lt;/strong&gt; The &lt;code&gt;[@@ &amp;quot;opaque_to_smt&amp;quot;]&lt;/code&gt; attribute on line 96 prevents Z3 from unfolding the function body. The solver never sees the &lt;code&gt;x * x&lt;/code&gt; computation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. All lemmas are unproven axioms.&lt;/strong&gt; The file contains approximately five &lt;code&gt;val&lt;/code&gt; declarations that characterize &lt;code&gt;i16_mul_32extended&lt;/code&gt;&amp;rsquo;s behavior&amp;mdash;for example, that multiplying by a power of two equals a left shift, or that multiplying by zero yields zero. These are stated as F* &lt;code&gt;val&lt;/code&gt; declarations without bodies. No corresponding &lt;code&gt;Spec.Intrinsics.fst&lt;/code&gt; implementation file exists. In F*, a &lt;code&gt;val&lt;/code&gt; without a corresponding &lt;code&gt;let&lt;/code&gt; in an implementation file is an axiom: the solver accepts it without proof. Every lemma about &lt;code&gt;i16_mul_32extended&lt;/code&gt; is an article of faith.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. The axioms claim correct $x \cdot y$ behavior.&lt;/strong&gt; The axioms describe what the function &lt;em&gt;should&lt;/em&gt; do (multiply &lt;code&gt;x&lt;/code&gt; by &lt;code&gt;y&lt;/code&gt;), not what it &lt;em&gt;actually&lt;/em&gt; does (square &lt;code&gt;x&lt;/code&gt;). Since the solver reasons from the axioms and never sees the definition, the contradiction is invisible. The axioms are logically false, but the solver has no way to discover this.&lt;/p&gt;
&lt;h3 id=&#34;impact-chain&#34;&gt;Impact Chain&lt;/h3&gt;
&lt;p&gt;The buggy function propagates through the specification:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;i16_mul_32extended_i16&lt;/code&gt;&lt;/strong&gt; (line 99) calls &lt;code&gt;i16_mul_32extended x y&lt;/code&gt;, inheriting the $x^2$ behavior.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;mm256_madd_epi16_lemma&lt;/code&gt;&lt;/strong&gt; (line 377) specifies AVX2&amp;rsquo;s &lt;code&gt;VPMADDWD&lt;/code&gt; instruction using &lt;code&gt;i16_mul_32extended&lt;/code&gt;. The claimed semantics are $a_{2k} \cdot b_{2k} + a_{2k+1} \cdot b_{2k+1}$; the actual definition computes $a_{2k}^2 + a_{2k+1}^2$, ignoring the second vector entirely.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;mm256_mullo_epi16_bv_lemma&lt;/code&gt;&lt;/strong&gt; (line 511) specifies AVX2&amp;rsquo;s &lt;code&gt;VPMULLW&lt;/code&gt; via &lt;code&gt;i16_mul_32extended_i16&lt;/code&gt;. Same issue: claims $a_i \cdot b_i$, computes $a_i^2$.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The ML-DSA build system&amp;rsquo;s Makefile lists nine AVX2 modules in &lt;code&gt;VERIFIED_MODULES&lt;/code&gt;, including &lt;code&gt;Simd.Avx2.Encoding.Error.fst&lt;/code&gt;, &lt;code&gt;Simd.Avx2.Encoding.Commitment.fst&lt;/code&gt;, &lt;code&gt;Simd.Avx2.Ntt.fst&lt;/code&gt;, and &lt;code&gt;Simd.Avx2.Arithmetic.fst&lt;/code&gt;. These modules are designated for full verification against the axioms in &lt;code&gt;Spec.Intrinsics.fsti&lt;/code&gt;. Any proofs built on these axioms inherit the unsoundness: the solver would accept incorrect code as correct, because it reasons from false premises.&lt;/p&gt;
&lt;p&gt;To put this plainly: every ML-DSA proof Cryspen will ever produce for their AVX2 backend&amp;mdash;the backend that runs on every modern x86 server and desktop&amp;mdash;is built on approximately forty axioms that are provably false, and no amount of future verification effort can fix this without first discovering and correcting the wrong definition that has been hiding in plain sight since the file was committed. If a customer, regulator, or certifying body relied on Cryspen&amp;rsquo;s &amp;ldquo;formally verified&amp;rdquo; designation to justify deploying libcrux ML-DSA in a context where digital signature correctness is legally or contractually mandated&amp;mdash;government procurement, financial infrastructure, firmware signing&amp;mdash;they did so on the basis of a verification foundation that is mathematically unsound, where the specification for multiplication itself computes the wrong operation.&lt;/p&gt;
&lt;h3 id=&#34;what-this-does-not-affect&#34;&gt;What This Does Not Affect&lt;/h3&gt;
&lt;p&gt;The runtime Rust code is unaffected. The actual ML-DSA implementation uses real Intel AVX2 intrinsics (&lt;code&gt;_mm256_madd_epi16&lt;/code&gt;, &lt;code&gt;_mm256_mullo_epi16&lt;/code&gt;) that perform correct $x \cdot y$ multiplication in hardware. The F* specification is never compiled into runtime code. This bug cannot cause incorrect signatures or verification failures in deployed software.&lt;/p&gt;
&lt;p&gt;The impact is on &lt;em&gt;verification trustworthiness&lt;/em&gt;: the formal proofs that are supposed to guarantee correctness of the AVX2 code path are built on a false foundation. The proofs would say &amp;ldquo;this code is correct&amp;rdquo; regardless of whether it actually is, because the axioms they reason from do not match the operations they claim to model.&lt;/p&gt;
&lt;h3 id=&#34;structural-comparison&#34;&gt;Structural Comparison&lt;/h3&gt;
&lt;p&gt;Findings 1 and 2 are runtime bugs in admitted (unverified) modules&amp;mdash;the verification system never looks at them. Finding 3 is different: the bug is &lt;em&gt;inside&lt;/em&gt; the verification infrastructure itself. The wrong specification was committed before the dependent proofs were written, establishing a false axiomatic foundation. Even running every proof with full solver dispatch would not catch it, because &lt;code&gt;val&lt;/code&gt; declarations without implementations are accepted by F* as axioms unconditionally.&lt;/p&gt;
&lt;p&gt;This is the same copy-paste pattern we have seen repeatedly: the ML-KEM decompression bug (V10) duplicated 1664 instead of writing &lt;code&gt;pow2 (d-1)&lt;/code&gt;, and the false serialization proof (V12) duplicated bound 1 instead of writing bound 12. Here, &lt;code&gt;cast x&lt;/code&gt; was duplicated instead of writing &lt;code&gt;cast y&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&#34;provenance-2&#34;&gt;Provenance&lt;/h3&gt;
&lt;p&gt;Introduced in commit &lt;a href=&#34;https://github.com/cryspen/libcrux/commit/cb165295de&#34;&gt;&lt;code&gt;cb165295de&lt;/code&gt;&lt;/a&gt; on May 21, 2025, by Lucas Franceschino, with the message &amp;ldquo;F*: mldsa: avx2: encoding: commitment.&amp;rdquo; The function was added as part of the ML-DSA AVX2 proof infrastructure. The bug has been present since the function&amp;rsquo;s introduction.&lt;/p&gt;
&lt;h2 id=&#34;the-pattern-continues&#34;&gt;The Pattern Continues&lt;/h2&gt;
&lt;p&gt;These three findings bring the total to eleven vulnerabilities across Cryspen&amp;rsquo;s cryptographic libraries in two weeks. The pattern we identified in our &lt;a href=&#34;https://symbolic.software/blog/2026-02-05-cryspen/&#34;&gt;original post&lt;/a&gt; and &lt;a href=&#34;https://eprint.iacr.org/2026/192&#34;&gt;paper&lt;/a&gt; remains consistent: bugs cluster in code that is unverified or lax-verified, while the marketing claims make no such distinction.&lt;/p&gt;
&lt;p&gt;Findings 1 and 2 are in ML-DSA&amp;rsquo;s core verification path&amp;mdash;the code that determines whether a digital signature is valid. Both are straightforward deviations from FIPS 204 that a careful comparison against the specification would catch. Both reside in modules that are admitted by default in the build system, meaning that even if someone wrote formal specifications for them, the proofs would not be checked.&lt;/p&gt;
&lt;p&gt;Finding 3 reveals a different failure mode: the bug is not in admitted code but in the verification specification itself. The axiomatic foundation for AVX2 proofs contains a wrong definition hidden behind an opacity barrier, with unproven lemmas that claim correct behavior. This is not a gap in verification coverage&amp;mdash;it is a corruption of the verification infrastructure.&lt;/p&gt;
&lt;p&gt;The ML-DSA Makefile makes the verification boundary explicit. Twenty-seven modules are in &lt;code&gt;VERIFIED_MODULES&lt;/code&gt;: SIMD arithmetic, NTT, encoding primitives, and type definitions. Everything else&amp;mdash;including the top-level signing and verification logic, signature encoding, and key generation&amp;mdash;is in &lt;code&gt;ADMIT_MODULES&lt;/code&gt;. The verified modules are the leaves. The roots are unverified. And as Finding 3 shows, even the verified leaves rest on unproven axioms.&lt;/p&gt;
&lt;p&gt;Cryspen&amp;rsquo;s response to our previous findings was to claim that &amp;ldquo;no bugs have been found in the verified code.&amp;rdquo; We &lt;a href=&#34;https://symbolic.software/blog/2026-02-12-cryspen-response/&#34;&gt;showed that this was false&lt;/a&gt;. Finding 3 reinforces this: the specification that the verified code is checked against is itself wrong. When the axioms are false, &amp;ldquo;verified&amp;rdquo; is an empty word.&lt;/p&gt;
&lt;p&gt;Formal verification is supposed to provide assurance that goes beyond testing. But assurance requires that the verification boundary be communicated honestly. When a library is marketed as &amp;ldquo;formally verified&amp;rdquo; and &amp;ldquo;high assurance,&amp;rdquo; users reasonably expect that the core cryptographic operations&amp;mdash;signing, verification, key generation&amp;mdash;are within that boundary. In libcrux&amp;rsquo;s ML-DSA, they are not. And even within that boundary, the axiomatic foundations have not been proved. Anyone who relied on Cryspen&amp;rsquo;s verification claims to justify deploying libcrux ML-DSA in a context where digital signature correctness carries legal or contractual obligations did so on the basis of a verification foundation where the specification for multiplication itself computes the wrong operation.&lt;/p&gt;





&lt;nav class=&#34;series-nav&#34; aria-label=&#34;Cryspen series (bottom)&#34;&gt;
  &lt;header class=&#34;series-nav-header&#34;&gt;
    &lt;span class=&#34;series-nav-label&#34;&gt;▶ Cryspen Series&lt;/span&gt;
    &lt;span class=&#34;series-nav-count&#34;&gt;5 posts · 2 papers · 1 talk&lt;/span&gt;
  &lt;/header&gt;
  &lt;ol class=&#34;series-nav-list&#34;&gt;
    
    
    &lt;li&gt;
      
      &lt;a href=&#34;https://symbolic.software/blog/2026-02-05-cryspen/&#34;&gt;On the Promises of &amp;#39;High-Assurance&amp;#39; Cryptography&lt;/a&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.02.05&lt;/span&gt;
    &lt;/li&gt;
    
    
    &lt;li&gt;
      
      &lt;a href=&#34;https://symbolic.software/blog/2026-02-12-cryspen-response/&#34;&gt;We Found Bugs in Cryspen&amp;#39;s Verified Code&lt;/a&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.02.12&lt;/span&gt;
    &lt;/li&gt;
    
    
    &lt;li class=&#34;current&#34; aria-current=&#34;page&#34;&gt;
      
      &lt;span class=&#34;title&#34;&gt;Even More Bugs in Cryspen&amp;#39;s libcrux: ML-DSA&lt;/span&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.02.17&lt;/span&gt;
    &lt;/li&gt;
    
    
    &lt;li&gt;
      
      &lt;a href=&#34;https://symbolic.software/blog/2026-03-07-cryspen-tls/&#34;&gt;Cryspen&amp;#39;s Approach to TLS: A Critical Analysis&lt;/a&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.03.07&lt;/span&gt;
    &lt;/li&gt;
    
    
    &lt;li&gt;
      
      &lt;a href=&#34;https://symbolic.software/blog/2026-04-07-cryspen-hax/&#34;&gt;The Verification Facade: Structural Gaps in Hax&lt;/a&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.04.07&lt;/span&gt;
    &lt;/li&gt;
    
  &lt;/ol&gt;
  &lt;div class=&#34;series-nav-footer&#34;&gt;
    &lt;div class=&#34;series-nav-row&#34;&gt;
      &lt;span class=&#34;series-nav-rowlabel&#34;&gt;Papers&lt;/span&gt;
      &lt;a href=&#34;https://eprint.iacr.org/2026/192&#34;&gt;&lt;em&gt;Verification Theatre&lt;/em&gt;&lt;/a&gt;
      &lt;span class=&#34;series-nav-sep&#34;&gt;·&lt;/span&gt;
      &lt;a href=&#34;https://eprint.iacr.org/2026/670&#34;&gt;&lt;em&gt;Verification Facade&lt;/em&gt;&lt;/a&gt;
    &lt;/div&gt;
    &lt;div class=&#34;series-nav-row&#34;&gt;
      &lt;span class=&#34;series-nav-rowlabel&#34;&gt;Talk&lt;/span&gt;
      &lt;a href=&#34;https://www.youtube.com/watch?v=TdOXza1-M_4&#34;&gt;&lt;em&gt;High Assurance Cryptography and the Ethics of Disclosure (OSTIF)&lt;/em&gt;&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/nav&gt;

</content:encoded>
      <category>Research</category>
      <category>Formal Verification</category>
      <category>Post-Quantum</category>
      
    </item>
    
    <item>
      <title>We Found Bugs in Cryspen&#39;s Verified Code</title>
      <link>https://symbolic.software/blog/2026-02-12-cryspen-response/</link>
      <pubDate>Thu, 12 Feb 2026 00:00:00 &#43;0000</pubDate>
      <guid>https://symbolic.software/blog/2026-02-12-cryspen-response/</guid>
      <description>Cryspen said they&#39;d be &#39;very interested&#39; if someone found a bug in their verified code. We found three.</description>
      <content:encoded>




&lt;nav class=&#34;series-nav&#34; aria-label=&#34;Cryspen series (top)&#34;&gt;
  &lt;header class=&#34;series-nav-header&#34;&gt;
    &lt;span class=&#34;series-nav-label&#34;&gt;▶ Cryspen Series&lt;/span&gt;
    &lt;span class=&#34;series-nav-count&#34;&gt;5 posts · 2 papers · 1 talk&lt;/span&gt;
  &lt;/header&gt;
  &lt;ol class=&#34;series-nav-list&#34;&gt;
    
    
    &lt;li&gt;
      
      &lt;a href=&#34;https://symbolic.software/blog/2026-02-05-cryspen/&#34;&gt;On the Promises of &amp;#39;High-Assurance&amp;#39; Cryptography&lt;/a&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.02.05&lt;/span&gt;
    &lt;/li&gt;
    
    
    &lt;li class=&#34;current&#34; aria-current=&#34;page&#34;&gt;
      
      &lt;span class=&#34;title&#34;&gt;We Found Bugs in Cryspen&amp;#39;s Verified Code&lt;/span&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.02.12&lt;/span&gt;
    &lt;/li&gt;
    
    
    &lt;li&gt;
      
      &lt;a href=&#34;https://symbolic.software/blog/2026-02-17-cryspen-mldsa/&#34;&gt;Even More Bugs in Cryspen&amp;#39;s libcrux: ML-DSA&lt;/a&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.02.17&lt;/span&gt;
    &lt;/li&gt;
    
    
    &lt;li&gt;
      
      &lt;a href=&#34;https://symbolic.software/blog/2026-03-07-cryspen-tls/&#34;&gt;Cryspen&amp;#39;s Approach to TLS: A Critical Analysis&lt;/a&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.03.07&lt;/span&gt;
    &lt;/li&gt;
    
    
    &lt;li&gt;
      
      &lt;a href=&#34;https://symbolic.software/blog/2026-04-07-cryspen-hax/&#34;&gt;The Verification Facade: Structural Gaps in Hax&lt;/a&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.04.07&lt;/span&gt;
    &lt;/li&gt;
    
  &lt;/ol&gt;
  &lt;div class=&#34;series-nav-footer&#34;&gt;
    &lt;div class=&#34;series-nav-row&#34;&gt;
      &lt;span class=&#34;series-nav-rowlabel&#34;&gt;Papers&lt;/span&gt;
      &lt;a href=&#34;https://eprint.iacr.org/2026/192&#34;&gt;&lt;em&gt;Verification Theatre&lt;/em&gt;&lt;/a&gt;
      &lt;span class=&#34;series-nav-sep&#34;&gt;·&lt;/span&gt;
      &lt;a href=&#34;https://eprint.iacr.org/2026/670&#34;&gt;&lt;em&gt;Verification Facade&lt;/em&gt;&lt;/a&gt;
    &lt;/div&gt;
    &lt;div class=&#34;series-nav-row&#34;&gt;
      &lt;span class=&#34;series-nav-rowlabel&#34;&gt;Talk&lt;/span&gt;
      &lt;a href=&#34;https://www.youtube.com/watch?v=TdOXza1-M_4&#34;&gt;&lt;em&gt;High Assurance Cryptography and the Ethics of Disclosure (OSTIF)&lt;/em&gt;&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/nav&gt;

&lt;p&gt;On February 5, we published &lt;a href=&#34;https://symbolic.software/blog/2026-02-05-cryspen/&#34;&gt;five new vulnerabilities&lt;/a&gt; in Cryspen&amp;rsquo;s libcrux, hpke-rs, and libcrux-psq cryptographic libraries, along with a &lt;a href=&#34;https://eprint.iacr.org/2026/192&#34;&gt;paper&lt;/a&gt; analyzing the structural pattern behind them. Cryspen&amp;rsquo;s response was to block our GitHub account, close our pull requests containing working fixes, and accuse us of acting in bad faith.&lt;/p&gt;
&lt;p&gt;One week later, on February 12, Cryspen published a 1,570-word response titled &amp;ldquo;&lt;a href=&#34;https://cryspen.com/post/strengths-and-limitations/&#34;&gt;The strengths and limits of formal verification&lt;/a&gt;.&amp;rdquo; In it, they write:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;So far, no bugs have been found in the verified code, although we would be very interested to know if any of our code generation tools inadvertently introduced a bug, and even more interested if the bug should have been caught by formal verification.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;We are happy to oblige. We forked libcrux and found three bugs in the verified code: two in the ML-KEM mathematical specification itself, and one in a correctness proof that Cryspen&amp;rsquo;s documentation claims is complete.&lt;/p&gt;
&lt;h2 id=&#34;what-cryspens-response-left-out&#34;&gt;What Cryspen&amp;rsquo;s Response Left Out&lt;/h2&gt;
&lt;p&gt;Before we get to the bugs, it is worth noting what Cryspen chose to address and what they chose to ignore. Their 1,570-word response acknowledged&amp;mdash;obliquely&amp;mdash;two of six vulnerabilities (one reported not by us, in November 2025):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;V1&lt;/strong&gt; (platform-dependent cryptographic output failure): described as &amp;ldquo;a fallback implementation for a platform-specific intrinsic.&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;V5&lt;/strong&gt; (Ed25519 double clamping): described as &amp;ldquo;one in an unverified wrapper around HACL* code.&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What they did not mention, in 1,570 words ostensibly about the strengths and limits of their verification methodology:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;V3: Nonce reuse via integer overflow.&lt;/strong&gt; A &lt;code&gt;u32&lt;/code&gt; sequence counter in hpke-rs that silently wraps to zero in release builds, reusing AEAD nonces. For AES-GCM, nonce reuse leaks the GHASH authentication key, enabling universal forgeries. For ChaCha20-Poly1305, it enables direct plaintext recovery. Not one word.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;V6: Denial of service via &lt;code&gt;.unwrap()&lt;/code&gt;.&lt;/strong&gt; A single malformed ciphertext crashes the process in the PSQ protocol&amp;rsquo;s AES-GCM decryption path. Breaks IND-CCA security for 9 of 18 supported PSQ ciphersuites. The correct error handling code was present but commented out. Not one word.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;V2: Missing X25519 all-zero validation.&lt;/strong&gt; An attacker can supply a low-order public key and force the HPKE shared secret to zero, making session keys deterministic and predictable. Forward secrecy completely broken. Not one word.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;V4: ECDSA signature malleability.&lt;/strong&gt; The ECDSA P-256 implementation lacks low-S normalization, a standard requirement for over a decade. Not one word.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The word &amp;ldquo;hpke-rs&amp;rdquo; does not appear anywhere in Cryspen&amp;rsquo;s response. This is the library that implements RFC 9180, the one depended upon by Signal&amp;rsquo;s &lt;code&gt;signal-crypto&lt;/code&gt; crate and by OpenMLS. Its two most severe vulnerabilities&amp;mdash;nonce reuse and broken forward secrecy&amp;mdash;were simply not mentioned.&lt;/p&gt;
&lt;p&gt;Cryspen wrote 1,570 words about &amp;ldquo;the strengths and limits of formal verification&amp;rdquo; without acknowledging four of the six vulnerabilities, including the two most dangerous ones.&lt;/p&gt;
&lt;p&gt;Four days after blocking our account and closing our pull requests, Cryspen&amp;rsquo;s CEO manually copied our fixes and merged them under his own name. The fixes that were characterized as &amp;ldquo;not a good-faith attempt to improve the project&amp;rsquo;s security posture&amp;rdquo; were adopted verbatim.&lt;/p&gt;
&lt;h2 id=&#34;bugs-in-the-verified-code&#34;&gt;Bugs in the Verified Code&lt;/h2&gt;
&lt;p&gt;With that context, let us return to Cryspen&amp;rsquo;s invitation. They said they would be &amp;ldquo;very interested&amp;rdquo; if someone found a bug that should have been caught by formal verification. We forked libcrux and looked.&lt;/p&gt;
&lt;p&gt;We found three.&lt;/p&gt;
&lt;h3 id=&#34;bug-1-wrong-specification-for-ml-kem-decompression&#34;&gt;Bug 1: Wrong Specification for ML-KEM Decompression&lt;/h3&gt;
&lt;p&gt;The F* specification function &lt;code&gt;decompress_d&lt;/code&gt; in &lt;code&gt;Spec.MLKEM.Math.fst&lt;/code&gt; (line 238) uses the wrong rounding constant. This is not a proof-level issue. This is the mathematical specification itself&amp;mdash;the correctness target for the entire ML-KEM decompression pipeline&amp;mdash;computing the wrong function.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;let decompress_d (d: dT {d &amp;lt;&amp;gt; 12}) (x: field_element_d d): field_element
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  = let r = (x * v v_FIELD_MODULUS + 1664) / pow2 d in
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    r                            -- ^^^^ should be: pow2 (d - 1)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When rounding integer division by $N$, the correct procedure is to add $N/2$ before dividing. For &lt;code&gt;decompress_d&lt;/code&gt;, the denominator is $2^d$, so the rounding constant should be $2^{d-1}$. The value $1664 = (q-1)/2$ is the correct rounding constant for the &lt;code&gt;compress_d&lt;/code&gt; function directly above it, which divides by $q = 3329$. The author used 1664 for both functions.&lt;/p&gt;
&lt;p&gt;Compare with &lt;code&gt;compress_d&lt;/code&gt; (line 225), where 1664 is correct:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;let compress_d (d: dT {d &amp;lt;&amp;gt; 12}) (x: field_element): field_element_d d
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  = let r = (pow2 d * x + 1664) / v v_FIELD_MODULUS in
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    ...                  -- ^^^^ correct here: dividing by q, so add q/2 = 1664
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The pattern is clear. For &lt;code&gt;compress_d&lt;/code&gt;, $N = q = 3329$, so $N/2 = 1664$. For &lt;code&gt;decompress_d&lt;/code&gt;, $N = 2^d$, so $N/2 = 2^{d-1}$. One constant was copied to both.&lt;/p&gt;
&lt;h4 id=&#34;concrete-counterexamples&#34;&gt;Concrete Counterexamples&lt;/h4&gt;
&lt;p&gt;FIPS 203 defines $\text{Decompress}_d(y) = \text{round}\!\left(\frac{q}{2^d} \cdot y\right)$, implemented as $\left\lfloor\frac{q \cdot y + 2^{d-1}}{2^d}\right\rfloor$.&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;d&lt;/th&gt;
          &lt;th&gt;x&lt;/th&gt;
          &lt;th&gt;Spec (wrong: 1664)&lt;/th&gt;
          &lt;th&gt;Correct ($2^{d-1}$)&lt;/th&gt;
          &lt;th&gt;FIPS 203&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;1&lt;/td&gt;
          &lt;td&gt;0&lt;/td&gt;
          &lt;td&gt;832&lt;/td&gt;
          &lt;td&gt;0&lt;/td&gt;
          &lt;td&gt;0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;1&lt;/td&gt;
          &lt;td&gt;1&lt;/td&gt;
          &lt;td&gt;2496&lt;/td&gt;
          &lt;td&gt;1665&lt;/td&gt;
          &lt;td&gt;1665&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;4&lt;/td&gt;
          &lt;td&gt;0&lt;/td&gt;
          &lt;td&gt;104&lt;/td&gt;
          &lt;td&gt;0&lt;/td&gt;
          &lt;td&gt;0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;4&lt;/td&gt;
          &lt;td&gt;1&lt;/td&gt;
          &lt;td&gt;312&lt;/td&gt;
          &lt;td&gt;208&lt;/td&gt;
          &lt;td&gt;208&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;10&lt;/td&gt;
          &lt;td&gt;0&lt;/td&gt;
          &lt;td&gt;1&lt;/td&gt;
          &lt;td&gt;0&lt;/td&gt;
          &lt;td&gt;0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;10&lt;/td&gt;
          &lt;td&gt;1&lt;/td&gt;
          &lt;td&gt;4&lt;/td&gt;
          &lt;td&gt;3&lt;/td&gt;
          &lt;td&gt;3&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The spec gives &lt;code&gt;decompress_d(d, 0) != 0&lt;/code&gt; for $d &lt; 12$. Decompressing zero should always yield zero. The spec and FIPS 203 disagree on every input for $d \in \{1, 4, 5, 10, 11\}$.&lt;/p&gt;
&lt;h4 id=&#34;the-implementation-is-correct-the-spec-is-wrong&#34;&gt;The Implementation Is Correct; the Spec Is Wrong&lt;/h4&gt;
&lt;p&gt;Libcrux&amp;rsquo;s own Rust implementation (&lt;code&gt;libcrux-ml-kem/src/vector/portable/compress.rs&lt;/code&gt;, line 317) uses the correct formula:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-rust&#34; data-lang=&#34;rust&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;mut&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;decompressed&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;a&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;elements&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;].&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;as_i32&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;no&#34;&gt;FIELD_MODULUS&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;classify&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;().&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;as_i32&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;decompressed&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;decompressed&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;+&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;i32&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;no&#34;&gt;COEFFICIENT_BITS&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;decompressed&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;decompressed&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;no&#34;&gt;COEFFICIENT_BITS&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;+&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This computes $(x \cdot q + 2^{d-1}) / 2^d$&amp;mdash;the correct rounding. The &lt;a href=&#34;https://github.com/pq-crystals/kyber&#34;&gt;CRYSTALS-Kyber reference implementation&lt;/a&gt; in C uses the same correct formula. The implementation is right. The specification is wrong. They disagree.&lt;/p&gt;
&lt;h4 id=&#34;why-it-goes-undetected&#34;&gt;Why It Goes Undetected&lt;/h4&gt;
&lt;p&gt;The spec function &lt;code&gt;decompress_d&lt;/code&gt; is the correctness target for the entire decompression pipeline. It is called via &lt;code&gt;byte_decode_then_decompress&lt;/code&gt; (line 270), which feeds into &lt;code&gt;decode_then_decompress_message&lt;/code&gt; ($d=1$), &lt;code&gt;decode_then_decompress_u&lt;/code&gt; ($d=d_u$), and &lt;code&gt;decode_then_decompress_v&lt;/code&gt; ($d=d_v$)&amp;mdash;every decompression path in ML-KEM encryption and decryption.&lt;/p&gt;
&lt;p&gt;The implementation&amp;rsquo;s &lt;code&gt;ensures&lt;/code&gt; clauses in &lt;code&gt;ind_cpa.rs&lt;/code&gt; reference these spec functions. But &lt;code&gt;Libcrux_ml_kem.Ind_cpa.fst&lt;/code&gt; is in &lt;code&gt;ADMIT_MODULES&lt;/code&gt; (line 4 of the extraction Makefile), so the proof that the implementation matches the spec is admitted&amp;mdash;the mismatch is never checked. Meanwhile, &lt;code&gt;verification_status.md&lt;/code&gt; claims ind_cpa is yes/yes/yes.&lt;/p&gt;
&lt;h4 id=&#34;provenance-and-impact&#34;&gt;Provenance and Impact&lt;/h4&gt;
&lt;p&gt;Introduced in commit &lt;a href=&#34;https://github.com/cryspen/libcrux/commit/c1935b9cf8&#34;&gt;&lt;code&gt;c1935b9cf8&lt;/code&gt;&lt;/a&gt; on August 12, 2024, by Karthikeyan Bhargavan (Cryspen&amp;rsquo;s Chief Research Scientist and corresponding author of their response post), with the commit message &amp;ldquo;spec&amp;rdquo;. This was the very first version of the ML-KEM specification file. The bug has been present since inception.&lt;/p&gt;
&lt;p&gt;This is a specification-level bug. The formal specification&amp;mdash;the mathematical reference that correctness proofs are verified against&amp;mdash;computes a different function than FIPS 203. Even if all proofs were completed and fully verified, they would prove the implementation correct against the wrong mathematical definition. The overall claim &amp;ldquo;ML-KEM implementation is correct with respect to FIPS 203&amp;rdquo; is undermined at the foundation.&lt;/p&gt;
&lt;p&gt;The fix is to change &lt;code&gt;1664&lt;/code&gt; to &lt;code&gt;pow2 (d - 1)&lt;/code&gt; on line 238:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; let decompress_d (d: dT {d &amp;lt;&amp;gt; 12}) (x: field_element_d d): field_element
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gd&#34;&gt;-  = let r = (x * v v_FIELD_MODULUS + 1664) / pow2 d in
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gd&#34;&gt;&lt;/span&gt;&lt;span class=&#34;gi&#34;&gt;+  = let r = (x * v v_FIELD_MODULUS + pow2 (d - 1)) / pow2 d in
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gi&#34;&gt;&lt;/span&gt;     r
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;bug-2-missing-inverse-ntt-in-ml-kem-encryption-specification&#34;&gt;Bug 2: Missing Inverse NTT in ML-KEM Encryption Specification&lt;/h3&gt;
&lt;p&gt;The F* specification function &lt;code&gt;ind_cpa_encrypt_unpacked&lt;/code&gt; in &lt;code&gt;Spec.MLKEM.fst&lt;/code&gt; (line 277) computes the ciphertext component &lt;code&gt;v&lt;/code&gt; by adding an NTT-domain polynomial directly to coefficient-domain polynomials, without first applying the inverse NTT. This is a domain mismatch&amp;mdash;the mathematical equivalent of adding a frequency-domain signal to a time-domain signal. The Rust implementation correctly applies &lt;code&gt;invert_ntt_montgomery&lt;/code&gt; before the addition.&lt;/p&gt;
&lt;p&gt;FIPS 203, Algorithm 14 (K-PKE.Encrypt), specifies two structurally identical computations:&lt;/p&gt;
$$\text{Step 19:} \quad \mathbf{u} \leftarrow \text{NTT}^{-1}(\hat{\mathbf{A}}^T \circ \hat{\mathbf{y}}) + \mathbf{e}_1$$$$\text{Step 21:} \quad \mathbf{v} \leftarrow \text{NTT}^{-1}(\hat{\mathbf{t}}^T \circ \hat{\mathbf{y}}) + \mathbf{e}_2 + \mu$$&lt;p&gt;Both require $\text{NTT}^{-1}$ on the NTT-domain product before adding coefficient-domain error terms. The specification implements step 19 correctly but omits $\text{NTT}^{-1}$ from step 21:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;-- Line 275 (u, correct):
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;let u = vector_add (vector_inv_ntt (matrix_vector_mul_ntt ...)) error_1
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                     ^^^^^^^^^^^^^^
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                     NTT⁻¹ applied
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;-- Line 277 (v, BUG):
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;let v = poly_add (poly_add (vector_dot_product_ntt t_as_ntt r_as_ntt) error_2) mu
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                             Returns NTT-domain. Missing: poly_inv_ntt
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The operand domains confirm the mismatch. &lt;code&gt;vector_dot_product_ntt&lt;/code&gt; computes $\hat{\mathbf{t}}^T \circ \hat{\mathbf{y}}$ via pointwise NTT-domain multiplication and summation (&lt;code&gt;vector_sum (vector_mul_ntt a b)&lt;/code&gt;, line 185), returning an NTT-domain polynomial. Meanwhile, &lt;code&gt;error_2&lt;/code&gt; (from &lt;code&gt;sample_poly_cbd2&lt;/code&gt;, line 165&amp;mdash;CBD sampling with no NTT) and &lt;code&gt;mu&lt;/code&gt; (from &lt;code&gt;decode_then_decompress_message&lt;/code&gt;, line 212&amp;mdash;byte decode and decompress) are both in the coefficient domain. The spec adds them directly.&lt;/p&gt;
&lt;p&gt;The decryption specification, thirty lines below, performs the same operation correctly:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;-- Line 307 (decryption, correct):
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;let w = poly_sub v (poly_inv_ntt (vector_dot_product_ntt secret_as_ntt (vector_ntt u)))
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                     ^^^^^^^^^^^^^^
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                     NTT⁻¹ applied
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;the-implementation-is-correct-the-spec-is-wrong-1&#34;&gt;The Implementation Is Correct; the Spec Is Wrong&lt;/h4&gt;
&lt;p&gt;The Rust implementation in &lt;code&gt;libcrux-ml-kem/src/matrix.rs&lt;/code&gt; (line 77) has a correct English comment:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-rust&#34; data-lang=&#34;rust&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;/// Compute InverseNTT(tᵀ ◦ r̂) + e₂ + message
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And the code (lines 99&amp;ndash;105) correctly applies the inverse NTT before the addition:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-rust&#34; data-lang=&#34;rust&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;in&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;..&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;K&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;product&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;t_as_ntt&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;].&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ntt_multiply&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;r_as_ntt&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;result&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;add_to_ring_element&lt;/span&gt;::&lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;K&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;product&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;invert_ntt_montgomery&lt;/span&gt;::&lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;K&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Vector&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;mut&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;result&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// NTT⁻¹ applied correctly
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;result&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;error_2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;add_message_error_reduce&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;message&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;result&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But its F* postcondition (line 88) matches the buggy spec, not the correct implementation:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;res_spec == Spec.MLKEM.(poly_add (poly_add (vector_dot_product_ntt ...) e2_spec) m_spec)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;--                                          ^^^ Missing poly_inv_ntt
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The programmer knew what the code should do. The English is right, the implementation is right, but the formal annotation is wrong.&lt;/p&gt;
&lt;h4 id=&#34;why-it-goes-undetected-1&#34;&gt;Why It Goes Undetected&lt;/h4&gt;
&lt;p&gt;The function &lt;code&gt;compute_ring_element_v&lt;/code&gt; is marked &lt;code&gt;verification_status(lax)&lt;/code&gt; (line 79 of &lt;code&gt;matrix.rs&lt;/code&gt;)&amp;mdash;its postcondition is only lax-checked, not proven. And &lt;code&gt;Libcrux_ml_kem.Ind_cpa.fst&lt;/code&gt; is in &lt;code&gt;ADMIT_MODULES&lt;/code&gt;, so the linking proof between the implementation and the specification is fully admitted.&lt;/p&gt;
&lt;h4 id=&#34;provenance-and-impact-1&#34;&gt;Provenance and Impact&lt;/h4&gt;
&lt;p&gt;This bug was introduced in the same commit as Bug 1: &lt;a href=&#34;https://github.com/cryspen/libcrux/commit/c1935b9cf8&#34;&gt;&lt;code&gt;c1935b9cf8&lt;/code&gt;&lt;/a&gt; on August 12, 2024, the initial creation of the ML-KEM specification file by Karthikeyan Bhargavan.&lt;/p&gt;
&lt;p&gt;This is a specification-level bug in ML-KEM encryption. The NTT representation of a polynomial is a completely different sequence of 256 field elements than its coefficient representation. Adding them directly produces a result that is neither the correct NTT-domain nor coefficient-domain value&amp;mdash;the spec computes a ciphertext &lt;code&gt;v&lt;/code&gt; that differs from what FIPS 203 prescribes for virtually all inputs. Both the &lt;code&gt;u&lt;/code&gt; computation (line 275) and the decryption (line 307) correctly apply the inverse NTT, making this an inconsistency within the specification itself.&lt;/p&gt;
&lt;p&gt;The fix is to add &lt;code&gt;poly_inv_ntt&lt;/code&gt; on line 277:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gd&#34;&gt;-    let v = poly_add (poly_add (vector_dot_product_ntt t_as_ntt r_as_ntt) error_2) mu in
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gd&#34;&gt;&lt;/span&gt;&lt;span class=&#34;gi&#34;&gt;+    let v = poly_add (poly_add (poly_inv_ntt (vector_dot_product_ntt t_as_ntt r_as_ntt)) error_2) mu in
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;bug-3-false-proof-in-ml-kem-serialization&#34;&gt;Bug 3: False Proof in ML-KEM Serialization&lt;/h3&gt;
&lt;p&gt;The F* lemma &lt;code&gt;deserialize_12_bit_vec_lemma_bounded&lt;/code&gt; in &lt;code&gt;libcrux-ml-kem/src/vector/portable/serialize.rs&lt;/code&gt; (line 691) claims that all elements produced by &lt;code&gt;deserialize_12&lt;/code&gt; are bounded by &lt;strong&gt;1&lt;/strong&gt;&amp;mdash;that is, they fit in a single bit. This is mathematically false. The &lt;code&gt;deserialize_12&lt;/code&gt; function extracts 12-bit values from a byte array; its outputs range from 0 to 4095. The bound should be &lt;strong&gt;12&lt;/strong&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;let deserialize_12_bit_vec_lemma_bounded (v: t_Array u8 (sz 24))
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  : squash (
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    let result = ${deserialize_12} v in
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    (forall (i: nat {i &amp;lt; 16}).
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      Rust_primitives.bounded (Seq.index result.f_elements i) 1)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                                                               ^
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  ) =                                      should be 12, not 1
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; _ by (Tactics.GetBit.prove_bit_vector_equality&amp;#39; ())
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is not a subtle semantic question. The lemma asserts that a function producing 12-bit values only ever produces values that fit in 1 bit. It is a false mathematical statement embedded in a proof that Cryspen&amp;rsquo;s own documentation claims is complete.&lt;/p&gt;
&lt;p&gt;The interface-level lemma &lt;code&gt;deserialize_12_lemma&lt;/code&gt; (line 706) correctly states the bound as 12. But it calls &lt;code&gt;deserialize_12_bit_vec_lemma_bounded&lt;/code&gt; to discharge its proof obligation, so the entire correctness proof for &lt;code&gt;deserialize_12&lt;/code&gt; rests on a false statement:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;let deserialize_12_lemma inputs =
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  deserialize_12_bit_vec_lemma inputs;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  deserialize_12_bit_vec_lemma_bounded inputs;   &amp;lt;-- false lemma
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  BitVecEq.bit_vec_equal_intro ...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;a-copy-paste-bug&#34;&gt;A Copy-Paste Bug&lt;/h4&gt;
&lt;p&gt;Every other deserializer in the same file has the correct bound. The &lt;code&gt;1&lt;/code&gt; was copied from &lt;code&gt;deserialize_1&lt;/code&gt; and never updated:&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Lemma&lt;/th&gt;
          &lt;th&gt;Bound&lt;/th&gt;
          &lt;th&gt;Correct?&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;deserialize_1_bit_vec_lemma&lt;/code&gt; (line 108)&lt;/td&gt;
          &lt;td&gt;1&lt;/td&gt;
          &lt;td&gt;Yes&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;deserialize_4_bit_vec_lemma_bounded&lt;/code&gt; (line 274)&lt;/td&gt;
          &lt;td&gt;4&lt;/td&gt;
          &lt;td&gt;Yes&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;deserialize_10_bit_vec_lemma_bounded&lt;/code&gt; (line 478)&lt;/td&gt;
          &lt;td&gt;10&lt;/td&gt;
          &lt;td&gt;Yes&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;deserialize_12_bit_vec_lemma_bounded&lt;/code&gt; (line 691)&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;No&lt;/strong&gt;&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The bug was introduced in commit &lt;a href=&#34;https://github.com/cryspen/libcrux/commit/5f6e3c2083&#34;&gt;&lt;code&gt;5f6e3c2083&lt;/code&gt;&lt;/a&gt; on March 25, 2025, by a Cryspen developer, with the commit message: &amp;ldquo;fix(ml-kem): deserialize_10/12: remove &lt;code&gt;admit&lt;/code&gt;.&amp;rdquo; The commit replaced &lt;code&gt;admit()&lt;/code&gt; calls with dedicated bounded lemmas. The &lt;code&gt;deserialize_10&lt;/code&gt; version was added correctly with bound 10. The &lt;code&gt;deserialize_12&lt;/code&gt; version was copy-pasted from &lt;code&gt;deserialize_1&lt;/code&gt; with the bound left as &lt;code&gt;1&lt;/code&gt;. The irony of a commit titled &amp;ldquo;remove admit&amp;rdquo; introducing a false proof should not be lost.&lt;/p&gt;
&lt;h4 id=&#34;three-layers-of-silent-failure&#34;&gt;Three Layers of Silent Failure&lt;/h4&gt;
&lt;p&gt;The false lemma goes undetected because three independent mechanisms conspire to prevent it from ever being checked:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. &amp;ldquo;Slow modules&amp;rdquo; are admitted by default.&lt;/strong&gt; In &lt;code&gt;fstar-helpers/Makefile.base&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-makefile&#34; data-lang=&#34;makefile&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;VERIFY_SLOW_MODULES&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;?=&lt;/span&gt; no
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;err&#34;&gt;ifeq&lt;/span&gt; &lt;span class=&#34;err&#34;&gt;(${VERIFY_SLOW_MODULES},no)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nv&#34;&gt;ADMIT_MODULES&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;+=&lt;/span&gt; &lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;SLOW_MODULES&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The portable serialize module is listed in &lt;code&gt;SLOW_MODULES&lt;/code&gt;. When &lt;code&gt;VERIFY_SLOW_MODULES&lt;/code&gt; is &lt;code&gt;no&lt;/code&gt;&amp;mdash;the default&amp;mdash;the module is added to &lt;code&gt;ADMIT_MODULES&lt;/code&gt; and verified with &lt;code&gt;--admit_smt_queries true&lt;/code&gt;, which accepts all SMT proofs without checking them.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. The proof tactic has a lax-mode escape hatch.&lt;/strong&gt; In &lt;code&gt;fstar-helpers/fstar-bitvec/Tactics.GetBit.fst&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;let prove_bit_vector_equality&amp;#39; (): Tac unit =
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  if lax_on ()
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  then iterAll tadmit
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  else prove_bit_vector_equality&amp;#39;&amp;#39; ()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When the module is lax-checked, the tactic calls &lt;code&gt;tadmit&lt;/code&gt; on all goals unconditionally, bypassing the actual bit-vector equality prover entirely. The false lemma is never subjected to any mathematical scrutiny.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Full verification is never triggered.&lt;/strong&gt; A search of the entire libcrux repository finds that &lt;code&gt;VERIFY_SLOW_MODULES=yes&lt;/code&gt; is never set in any CI configuration, Makefile target, or script. The proof is never actually checked in any automated pipeline.&lt;/p&gt;
&lt;h4 id=&#34;the-documentation-says-yes&#34;&gt;The Documentation Says &amp;ldquo;Yes&amp;rdquo;&lt;/h4&gt;
&lt;p&gt;Meanwhile, Cryspen&amp;rsquo;s own &lt;code&gt;verification_status.md&lt;/code&gt; claims the portable serialize module is fully verified:&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Category&lt;/th&gt;
          &lt;th&gt;File&lt;/th&gt;
          &lt;th&gt;Lax Checking&lt;/th&gt;
          &lt;th&gt;Runtime Safety&lt;/th&gt;
          &lt;th&gt;Correctness&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;em&gt;Portable&lt;/em&gt;&lt;/td&gt;
          &lt;td&gt;serialize&lt;/td&gt;
          &lt;td&gt;yes&lt;/td&gt;
          &lt;td&gt;yes&lt;/td&gt;
          &lt;td&gt;yes&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Three &amp;ldquo;yes&amp;rdquo; entries. Full verification claimed for lax checking, runtime safety, and correctness.&lt;/p&gt;
&lt;p&gt;But the file&amp;rsquo;s own header comment says:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;//! Verification status: Lax
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And the build system admits the module by default. So we have three sources that contradict each other:&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Source&lt;/th&gt;
          &lt;th&gt;Claimed status&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;verification_status.md&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;Fully verified (yes / yes / yes)&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;File header comment&lt;/td&gt;
          &lt;td&gt;Lax&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Actual build configuration&lt;/td&gt;
          &lt;td&gt;Admitted by default&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The code that Cryspen documents as formally verified for correctness contains a false mathematical statement that is silently accepted because the proof is never actually checked.&lt;/p&gt;
&lt;p&gt;The fix is to change &lt;code&gt;1&lt;/code&gt; to &lt;code&gt;12&lt;/code&gt; on line 691:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; let deserialize_12_bit_vec_lemma_bounded (v: t_Array u8 (sz 24))
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;   : squash (
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;     let result = ${deserialize_12} v in
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gd&#34;&gt;-    (forall (i: nat {i &amp;lt; 16}). Rust_primitives.bounded (Seq.index result.f_elements i) 1)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gd&#34;&gt;&lt;/span&gt;&lt;span class=&#34;gi&#34;&gt;+    (forall (i: nat {i &amp;lt; 16}). Rust_primitives.bounded (Seq.index result.f_elements i) 12)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gi&#34;&gt;&lt;/span&gt;   ) =
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  _ by (Tactics.GetBit.prove_bit_vector_equality&amp;#39; ())
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;what-this-means&#34;&gt;What This Means&lt;/h2&gt;
&lt;p&gt;Cryspen invited scrutiny of their verified code. We accepted the invitation, and it took one fork and a careful read to find:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;A wrong mathematical specification&lt;/strong&gt; for ML-KEM decompression&amp;mdash;the correctness target that the entire verification effort is built on top of&amp;mdash;that disagrees with FIPS 203 on every input and has been wrong since the spec file was first created, authored by Cryspen&amp;rsquo;s Chief Research Scientist himself.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;A missing inverse NTT&lt;/strong&gt; in the ML-KEM encryption specification that adds an NTT-domain polynomial to coefficient-domain polynomials&amp;mdash;mathematically nonsensical&amp;mdash;introduced in the same commit by the same author.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;A false proof annotation&lt;/strong&gt; in ML-KEM&amp;rsquo;s portable serialization that claims 12-bit values fit in 1 bit, silently accepted by a build system that defaults to not checking proofs.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;All three bugs survive for the same structural reason: the modules that would expose them are admitted by default. The proofs are never checked. The documentation says &amp;ldquo;yes&amp;rdquo; when the answer is &amp;ldquo;no.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Cryspen&amp;rsquo;s response to our original findings was to block us, close our fixes, and then quietly adopt them. Their public response acknowledged two of six vulnerabilities and ignored the four most severe. Their verification status documentation claims that code is verified when it is not. Their proof infrastructure silently admits false mathematical statements rather than checking them. And their mathematical specification&amp;mdash;the foundation on which every correctness claim rests&amp;mdash;computes a different function than the standard it claims to formalize, in two independent places.&lt;/p&gt;
&lt;p&gt;The formal verification methodology that Cryspen markets as providing &amp;ldquo;the highest level of assurance&amp;rdquo; contains a build system that defaults to not checking proofs, a tactic library that silently accepts all claims in lax mode, specifications that disagree with the standards they model, and documentation that says &amp;ldquo;yes&amp;rdquo; when the answer is &amp;ldquo;no.&amp;rdquo; The result is a formally verified cryptographic library where wrong specifications and false proofs survive indefinitely because no one&amp;mdash;and no automated system&amp;mdash;ever actually verifies them.&lt;/p&gt;
&lt;p&gt;We continue to believe that formal verification is a valuable technique when applied honestly and communicated precisely. What we object to is the gap between what is claimed and what is delivered. That gap is not a limitation of formal methods. It is a choice.&lt;/p&gt;





&lt;nav class=&#34;series-nav&#34; aria-label=&#34;Cryspen series (bottom)&#34;&gt;
  &lt;header class=&#34;series-nav-header&#34;&gt;
    &lt;span class=&#34;series-nav-label&#34;&gt;▶ Cryspen Series&lt;/span&gt;
    &lt;span class=&#34;series-nav-count&#34;&gt;5 posts · 2 papers · 1 talk&lt;/span&gt;
  &lt;/header&gt;
  &lt;ol class=&#34;series-nav-list&#34;&gt;
    
    
    &lt;li&gt;
      
      &lt;a href=&#34;https://symbolic.software/blog/2026-02-05-cryspen/&#34;&gt;On the Promises of &amp;#39;High-Assurance&amp;#39; Cryptography&lt;/a&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.02.05&lt;/span&gt;
    &lt;/li&gt;
    
    
    &lt;li class=&#34;current&#34; aria-current=&#34;page&#34;&gt;
      
      &lt;span class=&#34;title&#34;&gt;We Found Bugs in Cryspen&amp;#39;s Verified Code&lt;/span&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.02.12&lt;/span&gt;
    &lt;/li&gt;
    
    
    &lt;li&gt;
      
      &lt;a href=&#34;https://symbolic.software/blog/2026-02-17-cryspen-mldsa/&#34;&gt;Even More Bugs in Cryspen&amp;#39;s libcrux: ML-DSA&lt;/a&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.02.17&lt;/span&gt;
    &lt;/li&gt;
    
    
    &lt;li&gt;
      
      &lt;a href=&#34;https://symbolic.software/blog/2026-03-07-cryspen-tls/&#34;&gt;Cryspen&amp;#39;s Approach to TLS: A Critical Analysis&lt;/a&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.03.07&lt;/span&gt;
    &lt;/li&gt;
    
    
    &lt;li&gt;
      
      &lt;a href=&#34;https://symbolic.software/blog/2026-04-07-cryspen-hax/&#34;&gt;The Verification Facade: Structural Gaps in Hax&lt;/a&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.04.07&lt;/span&gt;
    &lt;/li&gt;
    
  &lt;/ol&gt;
  &lt;div class=&#34;series-nav-footer&#34;&gt;
    &lt;div class=&#34;series-nav-row&#34;&gt;
      &lt;span class=&#34;series-nav-rowlabel&#34;&gt;Papers&lt;/span&gt;
      &lt;a href=&#34;https://eprint.iacr.org/2026/192&#34;&gt;&lt;em&gt;Verification Theatre&lt;/em&gt;&lt;/a&gt;
      &lt;span class=&#34;series-nav-sep&#34;&gt;·&lt;/span&gt;
      &lt;a href=&#34;https://eprint.iacr.org/2026/670&#34;&gt;&lt;em&gt;Verification Facade&lt;/em&gt;&lt;/a&gt;
    &lt;/div&gt;
    &lt;div class=&#34;series-nav-row&#34;&gt;
      &lt;span class=&#34;series-nav-rowlabel&#34;&gt;Talk&lt;/span&gt;
      &lt;a href=&#34;https://www.youtube.com/watch?v=TdOXza1-M_4&#34;&gt;&lt;em&gt;High Assurance Cryptography and the Ethics of Disclosure (OSTIF)&lt;/em&gt;&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/nav&gt;

</content:encoded>
      <category>Research</category>
      <category>Formal Verification</category>
      
    </item>
    
    <item>
      <title>Verifpal Verifies Signal Across Three Messages</title>
      <link>https://symbolic.software/blog/2026-02-10-verifpal-signal/</link>
      <pubDate>Tue, 10 Feb 2026 00:00:00 &#43;0000</pubDate>
      <guid>https://symbolic.software/blog/2026-02-10-verifpal-signal/</guid>
      <description>Verifpal 0.31.2 ships a major overhaul to active attacker analysis, finally enabling full verification of Signal&#39;s three-message protocol.</description>
      <content:encoded>&lt;p&gt;Today we&amp;rsquo;re releasing Verifpal 0.31.2 which represents the most significant upgrade to Verifpal&amp;rsquo;s analysis engine since its inception. The headline result: &lt;strong&gt;for the first time, Verifpal can fully verify a model of Signal&amp;rsquo;s X3DH key agreement and Double Ratchet protocol across three messages&lt;/strong&gt;, under an active attacker, and it terminates in minutes.&lt;/p&gt;
&lt;p&gt;The verified models are published on VerifHub:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://verifhub.verifpal.com/7b33a06c46b93d14cefc36d417386101&#34;&gt;&lt;strong&gt;signal.vp&lt;/strong&gt; — Guarded long-term keys&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://verifhub.verifpal.com/35335d609b1de8cf8138c1e534356697&#34;&gt;&lt;strong&gt;signal_unguarded.vp&lt;/strong&gt; — Unguarded long-term keys&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This isn&amp;rsquo;t a novel research result: I myself &lt;a href=&#34;https://bblanche.gitlabpages.inria.fr/publications/KobeissiBhargavanBlanchetEuroSP17.pdf&#34;&gt;analyzed a similar Signal model using ProVerif back in 2016&lt;/a&gt; — a full decade ago. Tools like ProVerif and Tamarin have been able to handle models of this complexity for years.&lt;/p&gt;
&lt;p&gt;But Verifpal was never built to compete with ProVerif or Tamarin. It was built as a learning tool — accessible to undergraduates and first-year graduate students who are encountering protocol verification for the first time. What matters is that Verifpal can now handle models complex enough to be pedagogically interesting, and Signal is the canonical example.&lt;/p&gt;
&lt;h2 id=&#34;what-the-models-show&#34;&gt;What the Models Show&lt;/h2&gt;
&lt;p&gt;The Signal model captures the full three-message flow: X3DH key agreement followed by two rounds of Double Ratchet.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Message 1 (Bob → Alice): Pre-key bundle.&lt;/strong&gt; Bob publishes his long-term identity key, a signed semi-static pre-key, and a one-time pre-key. Alice verifies the signature and computes a master secret from four Diffie-Hellman computations — the core of X3DH.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Message 2 (Alice → Bob): Initial ciphertext.&lt;/strong&gt; Alice derives chain and message keys via HKDF, then encrypts her first message using AEAD with associated data binding both parties&amp;rsquo; identity keys and her current ephemeral key.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Message 3 (Bob → Alice) and Message 4 (Alice → Bob): Double Ratchet.&lt;/strong&gt; Each subsequent message introduces a new ephemeral key, performs a DH ratchet step, and derives fresh encryption keys through the HKDF-MAC chain.&lt;/p&gt;
&lt;p&gt;After all messages are exchanged, the model enters a post-compromise phase where both Alice&amp;rsquo;s and Bob&amp;rsquo;s long-term private keys are leaked, testing forward secrecy.&lt;/p&gt;
&lt;h3 id=&#34;guarded-model-results&#34;&gt;Guarded Model Results&lt;/h3&gt;
&lt;p&gt;In the &lt;a href=&#34;https://verifhub.verifpal.com/7b33a06c46b93d14cefc36d417386101&#34;&gt;guarded model&lt;/a&gt;, long-term identity keys are distributed over an authenticated channel (denoted by &lt;code&gt;[brackets]&lt;/code&gt; in Verifpal syntax, meaning the active attacker cannot substitute them):&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Query&lt;/th&gt;
          &lt;th&gt;Result&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;confidentiality? m1&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;Pass&lt;/strong&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;authentication? Alice -&amp;gt; Bob: e1&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;Fail&lt;/strong&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;confidentiality? m2&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;Pass&lt;/strong&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;authentication? Bob -&amp;gt; Alice: e2&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;Pass&lt;/strong&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;confidentiality? m3&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;Pass&lt;/strong&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;authentication? Alice -&amp;gt; Bob: e3&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;Pass&lt;/strong&gt;&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Five of six queries pass. The single failure — authentication of Alice&amp;rsquo;s first message to Bob — is a well-known property of the X3DH handshake. Because X3DH is designed for asynchronous messaging (Bob may be offline when Alice initiates), Alice&amp;rsquo;s first message cannot be fully authenticated until Bob responds and the ratchet completes a full round-trip. This is documented in the Signal specification and is not a vulnerability; it&amp;rsquo;s an inherent tradeoff of the asynchronous design.&lt;/p&gt;
&lt;p&gt;All three confidentiality queries pass even after long-term key compromise, confirming that the protocol provides forward secrecy.&lt;/p&gt;
&lt;h3 id=&#34;unguarded-model-results&#34;&gt;Unguarded Model Results&lt;/h3&gt;
&lt;p&gt;In the &lt;a href=&#34;https://verifhub.verifpal.com/35335d609b1de8cf8138c1e534356697&#34;&gt;unguarded model&lt;/a&gt;, long-term identity keys are sent over an unauthenticated channel, allowing the active attacker to substitute them:&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Query&lt;/th&gt;
          &lt;th&gt;Result&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;confidentiality? m1&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;Fail&lt;/strong&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;authentication? Alice -&amp;gt; Bob: e1&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;Fail&lt;/strong&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;confidentiality? m2&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;Fail&lt;/strong&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;authentication? Bob -&amp;gt; Alice: e2&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;Fail&lt;/strong&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;confidentiality? m3&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;Fail&lt;/strong&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;authentication? Alice -&amp;gt; Bob: e3&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;Fail&lt;/strong&gt;&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;All six queries fail. When an active attacker can man-in-the-middle both parties&amp;rsquo; long-term identity keys, every security property collapses — confidentiality and authentication alike, across all three messages. This is the expected and correct result: without an authenticated channel for identity key distribution, no amount of cryptographic ratcheting can save you.&lt;/p&gt;
&lt;h2 id=&#34;why-verifpal-couldnt-do-this-before&#34;&gt;Why Verifpal Couldn&amp;rsquo;t Do This Before&lt;/h2&gt;
&lt;p&gt;Verifpal&amp;rsquo;s active attacker analysis works by systematically mutating protocol values to candidate replacements drawn from the attacker&amp;rsquo;s knowledge. For each mutation combination, the engine checks whether the attacker can derive secrets, forge messages, or break authentication. The problem is combinatorial: as models grow in complexity, the number of mutation combinations explodes.&lt;/p&gt;
&lt;p&gt;Two sources of explosion were responsible for non-termination on the Signal model:&lt;/p&gt;
&lt;h3 id=&#34;mutation-map-cartesian-product-explosion&#34;&gt;Mutation Map Cartesian Product Explosion&lt;/h3&gt;
&lt;p&gt;The mutation map associates each mutable constant with a list of candidate replacement values. Previously, the verifier enumerated the &lt;strong&gt;full cartesian product&lt;/strong&gt; of all mutation lists simultaneously. For a model where four constants have mutation lists of sizes [2, 7, 7, 527], the full product is 51,646 combinations — each spawning a goroutine. At higher analysis stages, where injection expands these lists further, the product grows into the hundreds of thousands.&lt;/p&gt;
&lt;h3 id=&#34;injection-cartesian-product-explosion&#34;&gt;Injection Cartesian Product Explosion&lt;/h3&gt;
&lt;p&gt;The injection engine constructs candidate primitive values by combining known values into each argument slot. For a three-argument primitive like &lt;code&gt;AEAD_ENC&lt;/code&gt; where the attacker knows 20+ values per slot, the cartesian product can exceed 8,000 injected values — per primitive, per stage, per principal.&lt;/p&gt;
&lt;p&gt;Together, these two explosions caused the verifier to spawn millions of goroutines on the Signal model, leading to memory exhaustion, goroutine stack overflow, or effective non-termination.&lt;/p&gt;
&lt;h2 id=&#34;the-fixes&#34;&gt;The Fixes&lt;/h2&gt;
&lt;h3 id=&#34;weight-ordered-mutation-scanning&#34;&gt;Weight-Ordered Mutation Scanning&lt;/h3&gt;
&lt;p&gt;The core architectural change replaces brute-force cartesian product enumeration with a &lt;strong&gt;weight-ordered scanning strategy&lt;/strong&gt; that explores mutations in layers of increasing complexity.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Injection cap.&lt;/strong&gt; A hard limit of 500 injected values per primitive (&lt;code&gt;maxInjectionsPerPrimitive&lt;/code&gt;). The injection loop now pre-computes the total product size with overflow-safe arithmetic and breaks early once the cap is reached. This prevents memory exhaustion from a single &lt;code&gt;inject()&lt;/code&gt; call while preserving enough injection diversity for attack discovery.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mutation map subsetting.&lt;/strong&gt; Two new functions — &lt;code&gt;mutationMapSubset&lt;/code&gt; and &lt;code&gt;mutationMapSubsetCapped&lt;/code&gt; — allow the scanner to extract and budget-constrain subsets of the full mutation map. When a subset&amp;rsquo;s product exceeds the cap, each dimension is truncated to the &lt;em&gt;n&lt;/em&gt;th root of the limit (computed via binary search), distributing the budget evenly across dimensions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Weight-ordered scanning.&lt;/strong&gt; The new &lt;code&gt;verifyActiveScanWeighted&lt;/code&gt; function replaces direct cartesian product enumeration:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Weight 1:&lt;/strong&gt; Each mutable variable is tested alone, capped to 50 mutations per variable. This catches attacks that require substituting only a single value.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Weight 2:&lt;/strong&gt; Each pair of variables is tested simultaneously. Pairs whose product exceeds 20,000 are skipped entirely (not truncated), preserving the combinatorial structure of pairs that fit.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Weight 3:&lt;/strong&gt; Each triple of variables, same skip logic.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Full product:&lt;/strong&gt; Only attempted if the total product across all variables is within 50,000.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A per-principal-per-stage scan budget of 20,000 (tracked via atomic counter) prevents any single stage from consuming unbounded work. At most 50 subsets are scanned per weight level, preventing combinatorial blowup in &lt;code&gt;C(n, k)&lt;/code&gt; when the number of mutable variables is large.&lt;/p&gt;
&lt;p&gt;The maximum stage limit was also reduced from 64 to 8, ensuring the verifier declares exhaustion sooner when higher stages stop discovering new knowledge — critical for multi-phase models where phase 0 must exhaust before phase 1 analysis can begin.&lt;/p&gt;
&lt;h3 id=&#34;lock-free-concurrency-and-hash-based-lookups&#34;&gt;Lock-Free Concurrency and Hash-Based Lookups&lt;/h3&gt;
&lt;p&gt;The second fix targeted performance bottlenecks in the multithreaded analysis engine.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Lock-free attacker state snapshots.&lt;/strong&gt; The attacker state — the central data structure tracking everything the attacker knows — was previously guarded by a read-write mutex that became a bottleneck under high concurrency. The new implementation uses &lt;code&gt;atomic.Pointer[AttackerState]&lt;/code&gt; to publish immutable snapshots on every write. Read-side operations (&lt;code&gt;attackerStateGetRead&lt;/code&gt;, &lt;code&gt;attackerStateGetExhausted&lt;/code&gt;, &lt;code&gt;attackerStateGetKnownCount&lt;/code&gt;) now try the snapshot first, avoiding the read lock entirely. Write-side operations use optimistic double-checked locking: check under read lock, then acquire write lock only if necessary.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hash-based O(1) value lookups.&lt;/strong&gt; The &lt;code&gt;valueEquivalentValueInValues&lt;/code&gt; function — which checks whether a value already exists in the attacker&amp;rsquo;s known set — was a linear scan called in every hot path. It&amp;rsquo;s now replaced by &lt;code&gt;valueEquivalentValueInValuesMap&lt;/code&gt;, backed by an FNV-like hash map (&lt;code&gt;map[uint64][]int&lt;/code&gt;). Dedicated hashers handle primitives, equations, and constants, with special handling for the commutativity of three-element DH equations. The attacker state&amp;rsquo;s &lt;code&gt;KnownMap&lt;/code&gt; is maintained alongside the known values list and included in atomic snapshots.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Principal state clone optimization.&lt;/strong&gt; &lt;code&gt;constructPrincipalStateClone&lt;/code&gt; previously deep-copied every slice in the principal state. Since several slices (Constants, Guard, Known, Wire, KnownBy, DeclaredAt, MutatableTo, Phase) are immutable after initialization, they&amp;rsquo;re now shared by reference.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Parallel stage execution.&lt;/strong&gt; Analysis stages now run two at a time when possible, improving throughput on multi-core machines.&lt;/p&gt;
&lt;h3 id=&#34;aead-check-semantics-and-the-final-bug&#34;&gt;AEAD Check Semantics and the Final Bug&lt;/h3&gt;
&lt;p&gt;The last release fixed a subtle bug in how checked AEAD primitives were interpreted. In Verifpal, &lt;code&gt;AEAD_DEC(k, c, ad)?&lt;/code&gt; (with the &lt;code&gt;?&lt;/code&gt; suffix) means the decryption is &lt;em&gt;checked&lt;/em&gt; — the principal verifies that decryption succeeded and aborts if it doesn&amp;rsquo;t. Without the &lt;code&gt;?&lt;/code&gt;, the principal blindly accepts whatever comes out.&lt;/p&gt;
&lt;p&gt;The authentication and freshness query handlers were not correctly distinguishing between checked and unchecked AEAD decryptions, causing incorrect pass/fail indices. The fix adds an early-continue path for non-checked primitives in &lt;code&gt;queryAuthenticationGetPassIndices&lt;/code&gt; and &lt;code&gt;queryFreshness&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The Signal model itself was also corrected: three &lt;code&gt;AEAD_DEC&lt;/code&gt; calls that should have been checked (with &lt;code&gt;?&lt;/code&gt;) were missing the suffix, masking authentication failures that the analysis should have detected.&lt;/p&gt;
&lt;p&gt;This was the fix that made the Signal model terminate with correct results.&lt;/p&gt;
&lt;h2 id=&#34;what-this-means-for-verifpal&#34;&gt;What This Means for Verifpal&lt;/h2&gt;
&lt;p&gt;Once more, before academics try to strangle me (again): verifying Signal is not new. ProVerif could do this in 2016. Tamarin can do it. CryptoVerif can do it. Verifpal reaching this milestone in 2026 is not a claim of parity with these tools.&lt;/p&gt;
&lt;p&gt;What it is, is a claim of &lt;em&gt;relevance&lt;/em&gt;. Verifpal is used in university classrooms to teach protocol verification to students who have never seen a formal method before. When a student asks &amp;ldquo;can we model Signal?&amp;rdquo;, the answer is no longer &amp;ldquo;not in Verifpal.&amp;rdquo; That matters.&lt;/p&gt;
&lt;p&gt;The performance improvements also benefit every other Verifpal model. Analysis that previously timed out or exhausted memory will now terminate. Models with multi-argument primitives and complex key derivation chains — the bread and butter of real-world protocols — are now within reach.&lt;/p&gt;
&lt;p&gt;Verifpal 0.31.2 is available now. You can install it via &lt;a href=&#34;https://brew.sh&#34;&gt;Homebrew&lt;/a&gt;, &lt;a href=&#34;https://scoop.sh&#34;&gt;Scoop&lt;/a&gt;, or download it directly from &lt;a href=&#34;https://verifpal.com&#34;&gt;verifpal.com&lt;/a&gt;.&lt;/p&gt;
</content:encoded>
      <category>Software</category>
      <category>Research</category>
      <category>Verifpal</category>
      <category>Formal Verification</category>
      <category>Protocol Design</category>
      
    </item>
    
    <item>
      <title>On the Promises of &#39;High-Assurance&#39; Cryptography</title>
      <link>https://symbolic.software/blog/2026-02-05-cryspen/</link>
      <pubDate>Thu, 05 Feb 2026 00:00:00 &#43;0000</pubDate>
      <guid>https://symbolic.software/blog/2026-02-05-cryspen/</guid>
      <description>A case study on Cryspen&#39;s libcrux exposing the gap between formal verification marketing and engineering reality.</description>
      <content:encoded>




&lt;nav class=&#34;series-nav&#34; aria-label=&#34;Cryspen series (top)&#34;&gt;
  &lt;header class=&#34;series-nav-header&#34;&gt;
    &lt;span class=&#34;series-nav-label&#34;&gt;▶ Cryspen Series&lt;/span&gt;
    &lt;span class=&#34;series-nav-count&#34;&gt;5 posts · 2 papers · 1 talk&lt;/span&gt;
  &lt;/header&gt;
  &lt;ol class=&#34;series-nav-list&#34;&gt;
    
    
    &lt;li class=&#34;current&#34; aria-current=&#34;page&#34;&gt;
      
      &lt;span class=&#34;title&#34;&gt;On the Promises of &amp;#39;High-Assurance&amp;#39; Cryptography&lt;/span&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.02.05&lt;/span&gt;
    &lt;/li&gt;
    
    
    &lt;li&gt;
      
      &lt;a href=&#34;https://symbolic.software/blog/2026-02-12-cryspen-response/&#34;&gt;We Found Bugs in Cryspen&amp;#39;s Verified Code&lt;/a&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.02.12&lt;/span&gt;
    &lt;/li&gt;
    
    
    &lt;li&gt;
      
      &lt;a href=&#34;https://symbolic.software/blog/2026-02-17-cryspen-mldsa/&#34;&gt;Even More Bugs in Cryspen&amp;#39;s libcrux: ML-DSA&lt;/a&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.02.17&lt;/span&gt;
    &lt;/li&gt;
    
    
    &lt;li&gt;
      
      &lt;a href=&#34;https://symbolic.software/blog/2026-03-07-cryspen-tls/&#34;&gt;Cryspen&amp;#39;s Approach to TLS: A Critical Analysis&lt;/a&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.03.07&lt;/span&gt;
    &lt;/li&gt;
    
    
    &lt;li&gt;
      
      &lt;a href=&#34;https://symbolic.software/blog/2026-04-07-cryspen-hax/&#34;&gt;The Verification Facade: Structural Gaps in Hax&lt;/a&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.04.07&lt;/span&gt;
    &lt;/li&gt;
    
  &lt;/ol&gt;
  &lt;div class=&#34;series-nav-footer&#34;&gt;
    &lt;div class=&#34;series-nav-row&#34;&gt;
      &lt;span class=&#34;series-nav-rowlabel&#34;&gt;Papers&lt;/span&gt;
      &lt;a href=&#34;https://eprint.iacr.org/2026/192&#34;&gt;&lt;em&gt;Verification Theatre&lt;/em&gt;&lt;/a&gt;
      &lt;span class=&#34;series-nav-sep&#34;&gt;·&lt;/span&gt;
      &lt;a href=&#34;https://eprint.iacr.org/2026/670&#34;&gt;&lt;em&gt;Verification Facade&lt;/em&gt;&lt;/a&gt;
    &lt;/div&gt;
    &lt;div class=&#34;series-nav-row&#34;&gt;
      &lt;span class=&#34;series-nav-rowlabel&#34;&gt;Talk&lt;/span&gt;
      &lt;a href=&#34;https://www.youtube.com/watch?v=TdOXza1-M_4&#34;&gt;&lt;em&gt;High Assurance Cryptography and the Ethics of Disclosure (OSTIF)&lt;/em&gt;&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/nav&gt;

&lt;p&gt;&lt;strong&gt;Update (March 7, 2026):&lt;/strong&gt; A fifth finding has been added to this post: a denial-of-service vulnerability in libcrux-psq&amp;rsquo;s AES-GCM decryption path (Finding 5), discovered after the original publication.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://cryspen.com&#34;&gt;Cryspen&lt;/a&gt; is a company that markets itself as providing &amp;ldquo;services and software for high assurance software in order to establish trust into your critical systems.&amp;rdquo; Their flagship product, libcrux, is billed as a cryptographic library that &amp;ldquo;has been formally verified&amp;rdquo; and offers &amp;ldquo;the highest level of assurance&amp;rdquo;, translating into that it is safe to use and free of bugs.&lt;/p&gt;
&lt;p&gt;In the past two days, we at Symbolic Software have submitted four pull requests to Cryspen&amp;rsquo;s repositories addressing security vulnerabilities and implementation defects. These findings follow an incident from late 2025 in which libcrux produced silently incorrect cryptographic outputs on certain platforms: a bug that Cryspen addressed quietly, without any public disclosure, and which only received a security advisory because third parties took it upon themselves to file one.&lt;/p&gt;
&lt;p&gt;This pattern of events invites reflection on what &amp;ldquo;high assurance cryptography&amp;rdquo; actually means, and whether the formal verification community has developed a habit of overpromising and underdelivering.&lt;/p&gt;
&lt;h2 id=&#34;the-original-incident-platform-dependent-cryptographic-failures&#34;&gt;The Original Incident: Platform-Dependent Cryptographic Failures&lt;/h2&gt;
&lt;p&gt;In November 2025, Filippo Valsorda &lt;a href=&#34;https://github.com/cryspen/libcrux/issues/1220&#34;&gt;reported&lt;/a&gt; that libcrux-ml-dsa v0.0.3 produced different outputs depending on the execution environment. The same seed input generated different public keys and signatures on Alpine Linux with Ampere Altra ARM64 hardware compared to macOS on Apple Silicon. This was not an edge case in error handling or a performance regression—this was the core cryptographic functionality producing incorrect results.&lt;/p&gt;
&lt;p&gt;The bug resided in an unverified fallback implementation for the &lt;code&gt;vxarq_u64&lt;/code&gt; intrinsic. On platforms lacking native SHA-3 instruction support, the fallback passed incorrect arguments, corrupting SHA-3 digests and causing downstream cryptographic operations to fail silently. Any system relying on this code for ML-DSA signatures or ML-KEM key exchange would have experienced authentication failures or key agreement mismatches, depending on which platform generated the cryptographic material.&lt;/p&gt;
&lt;p&gt;Cryspen fixed the bug. What they did not do was issue any public disclosure, security advisory, or acknowledgment that their &amp;ldquo;formally verified&amp;rdquo; library had shipped with a defect that caused silent cryptographic failures in production environments.&lt;/p&gt;
&lt;h2 id=&#34;the-burial&#34;&gt;The Burial&lt;/h2&gt;
&lt;p&gt;The only security advisory that exists for this vulnerability was not initiated by Cryspen. It was &lt;a href=&#34;https://github.com/rustsec/advisory-db/pull/2493&#34;&gt;filed&lt;/a&gt; by Joe Birr-Pixton (ctz) in the RustSec advisory database—a third-party effort to document the vulnerability for the Rust ecosystem. The advisory was merged on December 4, 2025.&lt;/p&gt;
&lt;p&gt;When Cryspen&amp;rsquo;s maintainer engaged with the advisory process, they requested that the advisory target &lt;code&gt;libcrux-intrinsics&lt;/code&gt;, an internal dependency, rather than the user-facing &lt;code&gt;libcrux-ml-kem&lt;/code&gt; or &lt;code&gt;libcrux-ml-dsa&lt;/code&gt; crates. While technically accurate (the bug originated in the intrinsics crate), this framing minimizes the user-visible impact. Most developers who depend on &lt;code&gt;libcrux-ml-kem&lt;/code&gt; do not monitor advisories for transitive dependencies; targeting the advisory at the internal crate reduces the likelihood that affected users will learn of it through automated tools like &lt;code&gt;cargo audit&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This framing is technically accurate and practically misleading. The intrinsics crate is an internal dependency of libcrux-ml-kem and libcrux-ml-dsa. Users of those libraries experienced corrupted cryptographic operations. The distinction between &amp;ldquo;the intrinsic layer had a bug&amp;rdquo; and &amp;ldquo;the cryptographic library produced wrong outputs&amp;rdquo; matters to Cryspen&amp;rsquo;s marketing narrative but not to anyone whose systems were affected.&lt;/p&gt;
&lt;p&gt;Cryspen themselves made absolutely no announcement anywhere regarding the bug. There was no blog post. No security bulletin. No announcement on any Cryspen communication channel. No email to known users of the library. The RustSec advisory—which most developers will never see unless they actively run &lt;code&gt;cargo audit&lt;/code&gt;—represents the entirety of Cryspen&amp;rsquo;s disclosure posture for a bug that caused their formally verified cryptographic library to produce incorrect outputs.&lt;/p&gt;
&lt;p&gt;Compare this to how other cryptographic library maintainers handle similar situations. When the Kyber reference implementation contained a variable timing vulnerability in late 2023, it was the subject of extensive public discussion, a dedicated tracking website (&lt;a href=&#34;https://kyberslash.cr.yp.to/libraries.html&#34;&gt;KyberSlash&lt;/a&gt;), and advisories from affected downstream implementations. When we discovered the same vulnerability in our own &lt;a href=&#34;https://symbolic.software/blog/2023-12-19-kyberk2sovariabletiming/&#34;&gt;Kyber-K2SO&lt;/a&gt; implementation, we issued a public security announcement within 24 hours; we were among the first to do so.&lt;/p&gt;
&lt;p&gt;Cryspen&amp;rsquo;s approach was to fix the bug, avoid any public acknowledgment, and wait for someone else to file an advisory in an obscure database.&lt;/p&gt;
&lt;h2 id=&#34;five-new-findings&#34;&gt;Five New Findings&lt;/h2&gt;
&lt;p&gt;Between February 3 and February 4, 2026, we identified four additional issues in Cryspen&amp;rsquo;s cryptographic libraries, with a fifth discovered subsequently. These range from specification non-compliance to entropy reduction to potential nonce reuse to denial of service.&lt;/p&gt;
&lt;h3 id=&#34;1-missing-x25519-all-zero-validation-hpke-rs&#34;&gt;1. Missing X25519 All-Zero Validation (hpke-rs)&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/cryspen/hpke-rs/pull/117&#34;&gt;Pull request #117&lt;/a&gt; addresses a missing validation required by RFC 9180, the HPKE specification. Section 7.1.4 of the RFC mandates that implementations &amp;ldquo;MUST check whether the Diffie-Hellman shared secret is the all-zero value and abort if so.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;The X25519 function, when given certain low-order points as input (such as the identity element or points of small order), produces an all-zero output. If an attacker can supply a malicious public key, they can force the shared secret to be zero, making the subsequent key derivation deterministic and predictable.&lt;/p&gt;
&lt;p&gt;This vulnerability concerns an explicit requirement in the specification that the implementation claimed to follow. The fix is trivial: a single comparison after the DH computation. Its absence indicates that either the specification was not read carefully or the requirement was deprioritized.&lt;/p&gt;
&lt;h3 id=&#34;2-nonce-reuse-via-sequence-number-overflow-hpke-rs&#34;&gt;2. Nonce Reuse via Sequence Number Overflow (hpke-rs)&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/cryspen/hpke-rs/pull/118&#34;&gt;Pull request #118&lt;/a&gt; addresses a flaw in how HPKE sequence numbers are managed. The library stores the sequence number as a &lt;code&gt;u32&lt;/code&gt;, which has a maximum value of approximately 4.3 billion. RFC 9180 specifies that the sequence number must be checked against 2^96 - 1 for standard 12-byte nonces.&lt;/p&gt;
&lt;p&gt;The overflow check in the original code compared a &lt;code&gt;u32&lt;/code&gt; against a value that a &lt;code&gt;u32&lt;/code&gt; can never reach. In debug builds, Rust&amp;rsquo;s overflow checking would cause a panic when the counter wrapped. In release builds—the builds that actually run in production—the counter would silently wrap to zero, reusing nonces.&lt;/p&gt;
&lt;p&gt;Nonce reuse in authenticated encryption is catastrophic. For AES-GCM, it enables recovery of the authentication key and XOR of plaintexts. For ChaCha20-Poly1305, it directly enables plaintext recovery through XOR attacks. Any application encrypting more than 4.3 billion messages with the same HPKE context would be vulnerable.&lt;/p&gt;
&lt;p&gt;The fix is one line: replace &lt;code&gt;+&lt;/code&gt; with &lt;code&gt;checked_add()&lt;/code&gt;. The fact that this was not already the implementation suggests that the security implications of integer overflow in cryptographic contexts were not adequately considered.&lt;/p&gt;
&lt;h3 id=&#34;3-ecdsa-signature-malleability-libcrux&#34;&gt;3. ECDSA Signature Malleability (libcrux)&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/cryspen/libcrux/pull/1315&#34;&gt;Pull request #1315&lt;/a&gt; addresses signature malleability in the ECDSA P-256 implementation. For any valid ECDSA signature (r, s), there exists an alternative valid signature (r, n - s), where n is the curve order. Without low-S normalization—constraining s to be at most n/2—signatures are malleable: an attacker can produce alternative valid signatures without knowing the private key.&lt;/p&gt;
&lt;p&gt;This matters in practice. Bitcoin&amp;rsquo;s BIP 62 and BIP 146 mandate low-S normalization because signature malleability can alter transaction identifiers, potentially enabling theft and breaking transaction chains. More generally, any system that uses signatures for deduplication, audit trails, or caching can be confused by malleable signatures.&lt;/p&gt;
&lt;p&gt;The libcrux implementation validates that r and s are nonzero but does not compare s against n/2 or apply normalization. This is a well-known requirement that has been standard practice in ECDSA implementations for over a decade.&lt;/p&gt;
&lt;h3 id=&#34;4-ed25519-double-clamping-libcrux&#34;&gt;4. Ed25519 Double Clamping (libcrux)&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/cryspen/libcrux/pull/1316&#34;&gt;Pull request #1316&lt;/a&gt; addresses an unnecessary entropy reduction in Ed25519 key generation. The &lt;code&gt;generate_key_pair&lt;/code&gt; function was applying scalar clamping operations to the raw seed before hashing:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-rust&#34; data-lang=&#34;rust&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;sk&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;248&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;u8&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sk&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;31&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;127&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;u8&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sk&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;31&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;|=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;64&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;u8&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;According to RFC 8032, clamping should occur after SHA-512 hashing of the seed, not before. Applying these operations to the raw seed removes 5 bits of entropy (3 from the first byte, 2 from the last), reducing the effective key space from 256 bits to 251 bits.&lt;/p&gt;
&lt;p&gt;While 251 bits remains computationally secure against brute force, there is no benefit to this reduction. The SHA-512 hash produces a uniformly distributed output regardless of input patterns; clamping the input serves no cryptographic purpose. It only reduces entropy without providing any compensating advantage.&lt;/p&gt;
&lt;p&gt;This is not a subtle issue. The Ed25519 specification is explicit about when clamping occurs. Implementing it incorrectly suggests either that the specification was not consulted or that its requirements were misunderstood.&lt;/p&gt;
&lt;h3 id=&#34;5-denial-of-service-via-aes-gcm-decryption-panic-libcrux-psq&#34;&gt;5. Denial of Service via AES-GCM Decryption Panic (libcrux-psq)&lt;/h3&gt;
&lt;p&gt;The &lt;a href=&#34;https://github.com/cryspen/libcrux&#34;&gt;libcrux-psq&lt;/a&gt; crate implements a post-quantum pre-shared-key protocol. In the &lt;code&gt;decrypt_out&lt;/code&gt; method, the AES-GCM 128 decryption path calls &lt;code&gt;.unwrap()&lt;/code&gt; on the decryption result instead of propagating the error. A single malformed ciphertext crashes the process.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-rust&#34; data-lang=&#34;rust&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;AEADKey&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;AesGcm128&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;key&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;libcrux_aesgcm&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;AesGcm128&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;decrypt&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;plaintext&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;key&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;bp&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;nonce&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;aad&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ciphertext&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;tag&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;unwrap&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// .map_err(|_| AEADError::CryptoError)?;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The correct error handling is commented out directly below the &lt;code&gt;.unwrap()&lt;/code&gt; call. Beyond the denial-of-service impact, this defect renders the PSQ implementation not IND-CCA secure: the IND-CCA security game requires a decryption oracle that returns ⊥ on invalid ciphertexts, but a panicking implementation terminates execution instead, making the decryption oracle unavailable after the first invalid query.&lt;/p&gt;
&lt;p&gt;The verified AES-GCM primitive correctly returns an error on authentication failure. The unverified wrapper discards it. The fix is a &lt;a href=&#34;https://github.com/nadimkobeissi/libcrux/commit/96d3d8a751aa536a619476a8b45c7c12b51a74ec&#34;&gt;one-line change&lt;/a&gt;: replace &lt;code&gt;.unwrap()&lt;/code&gt; with the error propagation that was already written and commented out.&lt;/p&gt;
&lt;h2 id=&#34;what-does-this-tell-us-about-high-assurance-cryptography&#34;&gt;What Does This Tell Us About &amp;ldquo;High Assurance Cryptography&amp;rdquo;?&lt;/h2&gt;
&lt;p&gt;The formal verification community has developed a vocabulary for marketing that systematically overstates what verification actually accomplishes. When Cryspen claims their library is &amp;ldquo;formally verified&amp;rdquo; and &amp;ldquo;free of bugs,&amp;rdquo; they are making statements that their own bug history contradicts.&lt;/p&gt;
&lt;p&gt;The November 2025 incident is instructive. The bug was in an ARM SIMD intrinsic fallback—precisely the kind of low-level, platform-specific code that formal verification tools cannot reason about. Formal verification in practice means verifying an abstract model, then trusting that the compiler, the intrinsics, the runtime, and the hardware all faithfully implement that model.&lt;/p&gt;
&lt;p&gt;This is not verification in the sense that users understand the term. It is verification of a Rust model, plus hope that LLVM generates correct code, plus hope that the ARM instruction set behaves as documented, plus hope that every CPU implementation thereof is free of errata, plus hope that the operating system, memory allocator, and runtime environment introduce no observable differences.&lt;/p&gt;
&lt;p&gt;The honest description would be: &amp;ldquo;We have verified portions of our Rust code against certain properties using the hax toolchain. The actual security of deployed systems depends on your compiler version, target architecture, CPU microcode, operating system, and numerous other components we cannot verify.&amp;rdquo; But that does not sell. It does not win grants. It does not attract customers.&lt;/p&gt;
&lt;p&gt;Instead, we get &amp;ldquo;formally verified&amp;rdquo; as a marketing term, a way to claim superiority over libraries that rely on extensive testing, fuzzing, cross-platform validation, and code review—the engineering practices that might have caught the platform-dependent output bug before it shipped.&lt;/p&gt;
&lt;p&gt;The five new findings reinforce this pattern. None of them involve subtle interactions between verified and unverified code. They are straightforward implementation defects:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A specification requirement that was ignored (X25519 zero check).&lt;/li&gt;
&lt;li&gt;An integer type that was too small for its purpose (sequence number overflow).&lt;/li&gt;
&lt;li&gt;A normalization step that was omitted (ECDSA low-S).&lt;/li&gt;
&lt;li&gt;A clamping operation that was applied at the wrong point (Ed25519 double clamping).&lt;/li&gt;
&lt;li&gt;A verified primitive&amp;rsquo;s error discarded by an unverified wrapper (AES-GCM decryption panic).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are not the kinds of bugs that formal verification is designed to catch. They are the kinds of bugs that careful code review, comprehensive testing, and attention to specifications would catch. They suggest that the development process prioritized the verifiable core over the engineering fundamentals that surround it.&lt;/p&gt;
&lt;h2 id=&#34;the-verification-theater-problem&#34;&gt;The Verification Theater Problem&lt;/h2&gt;
&lt;p&gt;There is a pattern in the formal methods community that might be called &amp;ldquo;verification theater&amp;rdquo;: the deployment of formal methods in ways that create the appearance of rigor without delivering its substance.&lt;/p&gt;
&lt;p&gt;A cryptographic library that is &amp;ldquo;formally verified&amp;rdquo; but ships with platform-dependent incorrect outputs has not achieved high assurance. It has achieved a verified core surrounded by an unverified periphery that can fail in ways that make the verification meaningless.&lt;/p&gt;
&lt;p&gt;A company that fixes critical bugs without disclosure has not demonstrated trustworthiness. It has demonstrated that marketing concerns outweigh transparency obligations.&lt;/p&gt;
&lt;p&gt;A codebase that lacks basic specification compliance—checking for all-zero DH outputs, preventing integer overflow, normalizing signatures—has not been developed with the care that &amp;ldquo;highest level of assurance&amp;rdquo; implies.&lt;/p&gt;
&lt;p&gt;The formal verification community needs to develop more honest communication practices. Verification is valuable. It catches certain classes of bugs that testing cannot. But it is not a substitute for engineering discipline, and claiming otherwise undermines both the credibility of formal methods and the security of users who trust those claims.&lt;/p&gt;
&lt;p&gt;When a formally verified cryptographic library produces platform-dependent incorrect outputs for its core primitive, and that library&amp;rsquo;s maintainers bury the disclosure while continuing to market the library as &amp;ldquo;free of bugs,&amp;rdquo; we must ask: free of which bugs, verified against which properties, and trustworthy to whom?&lt;/p&gt;
&lt;p&gt;The answers, in Cryspen&amp;rsquo;s case, appear to be: not the bugs that matter, not the properties users care about, and not to anyone paying close attention.&lt;/p&gt;





&lt;nav class=&#34;series-nav&#34; aria-label=&#34;Cryspen series (bottom)&#34;&gt;
  &lt;header class=&#34;series-nav-header&#34;&gt;
    &lt;span class=&#34;series-nav-label&#34;&gt;▶ Cryspen Series&lt;/span&gt;
    &lt;span class=&#34;series-nav-count&#34;&gt;5 posts · 2 papers · 1 talk&lt;/span&gt;
  &lt;/header&gt;
  &lt;ol class=&#34;series-nav-list&#34;&gt;
    
    
    &lt;li class=&#34;current&#34; aria-current=&#34;page&#34;&gt;
      
      &lt;span class=&#34;title&#34;&gt;On the Promises of &amp;#39;High-Assurance&amp;#39; Cryptography&lt;/span&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.02.05&lt;/span&gt;
    &lt;/li&gt;
    
    
    &lt;li&gt;
      
      &lt;a href=&#34;https://symbolic.software/blog/2026-02-12-cryspen-response/&#34;&gt;We Found Bugs in Cryspen&amp;#39;s Verified Code&lt;/a&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.02.12&lt;/span&gt;
    &lt;/li&gt;
    
    
    &lt;li&gt;
      
      &lt;a href=&#34;https://symbolic.software/blog/2026-02-17-cryspen-mldsa/&#34;&gt;Even More Bugs in Cryspen&amp;#39;s libcrux: ML-DSA&lt;/a&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.02.17&lt;/span&gt;
    &lt;/li&gt;
    
    
    &lt;li&gt;
      
      &lt;a href=&#34;https://symbolic.software/blog/2026-03-07-cryspen-tls/&#34;&gt;Cryspen&amp;#39;s Approach to TLS: A Critical Analysis&lt;/a&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.03.07&lt;/span&gt;
    &lt;/li&gt;
    
    
    &lt;li&gt;
      
      &lt;a href=&#34;https://symbolic.software/blog/2026-04-07-cryspen-hax/&#34;&gt;The Verification Facade: Structural Gaps in Hax&lt;/a&gt;
      
      &lt;span class=&#34;date&#34;&gt;2026.04.07&lt;/span&gt;
    &lt;/li&gt;
    
  &lt;/ol&gt;
  &lt;div class=&#34;series-nav-footer&#34;&gt;
    &lt;div class=&#34;series-nav-row&#34;&gt;
      &lt;span class=&#34;series-nav-rowlabel&#34;&gt;Papers&lt;/span&gt;
      &lt;a href=&#34;https://eprint.iacr.org/2026/192&#34;&gt;&lt;em&gt;Verification Theatre&lt;/em&gt;&lt;/a&gt;
      &lt;span class=&#34;series-nav-sep&#34;&gt;·&lt;/span&gt;
      &lt;a href=&#34;https://eprint.iacr.org/2026/670&#34;&gt;&lt;em&gt;Verification Facade&lt;/em&gt;&lt;/a&gt;
    &lt;/div&gt;
    &lt;div class=&#34;series-nav-row&#34;&gt;
      &lt;span class=&#34;series-nav-rowlabel&#34;&gt;Talk&lt;/span&gt;
      &lt;a href=&#34;https://www.youtube.com/watch?v=TdOXza1-M_4&#34;&gt;&lt;em&gt;High Assurance Cryptography and the Ethics of Disclosure (OSTIF)&lt;/em&gt;&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/nav&gt;

</content:encoded>
      <category>Research</category>
      <category>Formal Verification</category>
      
    </item>
    
    <item>
      <title>Announcing Cedarcrypt: Applied Cryptography in the Mediterranean</title>
      <link>https://symbolic.software/blog/2026-02-03-cedarcrypt/</link>
      <pubDate>Tue, 03 Feb 2026 00:00:00 &#43;0000</pubDate>
      <guid>https://symbolic.software/blog/2026-02-03-cedarcrypt/</guid>
      <description>We&#39;re thrilled to announce Cedarcrypt, a new applied cryptography summer school and conference.</description>
      <content:encoded>&lt;p&gt;Come be part of Cedarcrypt, our historic new initiative to grow cryptography research, development and representation in the Levant region.&lt;/p&gt;
&lt;p&gt;For too long, the global cryptography community has concentrated its major events in a handful of locations, leaving entire regions underrepresented in the conversations that shape our digital future. Cedarcrypt is here to change that.&lt;/p&gt;
&lt;h2 id=&#34;what-is-cedarcrypt&#34;&gt;What is Cedarcrypt?&lt;/h2&gt;
&lt;p&gt;This July 13-16, 2026, we&amp;rsquo;re bringing together researchers, practitioners, and students at the American University of Beirut&amp;rsquo;s Mediterraneo campus in Paphos, Cyprus, for four days of intensive learning, knowledge sharing, and community building. From secure messaging protocols to post-quantum cryptography, from zero-knowledge proofs to formal verification, Cedarcrypt aims to cover the full spectrum of applied cryptography.&lt;/p&gt;
&lt;p&gt;Cedarcrypt is about planting a flag and telling the world that real cryptography work can and does emerge from our region. We aim to create a space where the next generation of cryptographers from the Levant and beyond can learn from established experts, present their own research, and forge connections that will shape their careers.&lt;/p&gt;
&lt;h2 id=&#34;call-for-submissions&#34;&gt;Call for Submissions&lt;/h2&gt;
&lt;p&gt;We need you to make this happen. We&amp;rsquo;re seeking:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Workshop leaders&lt;/strong&gt; to teach hands-on skills.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lecturers&lt;/strong&gt; to share foundational and cutting-edge knowledge.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Researchers&lt;/strong&gt; to present their latest work.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Whether you&amp;rsquo;re a seasoned professor or an early-career researcher with fresh ideas, there&amp;rsquo;s a place for you at Cedarcrypt.&lt;/p&gt;
&lt;p&gt;Cedarcrypt welcomes submissions across the full spectrum of applied cryptography. We&amp;rsquo;re particularly interested in work on secure messaging protocols and the challenges of building private communication systems at scale, as well as the rapidly evolving field of post-quantum cryptography as the community prepares for a post-quantum world. Zero-knowledge proofs and their applications in privacy-preserving systems are also a key area of focus.&lt;/p&gt;
&lt;p&gt;Beyond these core topics, we encourage submissions on formal verification of cryptographic implementations, secure implementation practices that bridge the gap between theoretical security and real-world code, and privacy technologies more broadly. If your work touches on how cryptography is built, deployed, or analyzed in practice, we want to hear from you.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://cedarcrypt.org/#cfp&#34;&gt;&lt;strong&gt;Submit your proposal!&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&#34;the-venue&#34;&gt;The Venue&lt;/h2&gt;
&lt;p&gt;Paphos, Cyprus offers a unique setting for Cedarcrypt. This UNESCO World Heritage city combines world-class beaches with a rich cultural heritage dating back thousands of years. The AUB Mediterraneo campus provides modern facilities in the heart of this historic city, with direct flights available from major European cities.&lt;/p&gt;
&lt;h2 id=&#34;get-involved&#34;&gt;Get Involved&lt;/h2&gt;
&lt;p&gt;This is the first edition of what we intend to become an annual tradition. Come be part of our history! Help us build something that will inspire and empower cryptographers for years to come.&lt;/p&gt;
&lt;p&gt;Visit &lt;a href=&#34;https://cedarcrypt.org&#34;&gt;cedarcrypt.org&lt;/a&gt; to learn more and submit your proposal.&lt;/p&gt;
</content:encoded>
      <category>Announcement</category>
      
    </item>
    
    <item>
      <title>Introducing Magicall: Encrypted video calls that actually work</title>
      <link>https://symbolic.software/blog/2026-01-05-magicall/</link>
      <pubDate>Mon, 05 Jan 2026 00:00:00 &#43;0000</pubDate>
      <guid>https://symbolic.software/blog/2026-01-05-magicall/</guid>
      <description>We&#39;re launching Magicall, a privacy-first video calling platform built by cryptographers.</description>
      <content:encoded>&lt;p&gt;Today, we&amp;rsquo;re publicly launching &lt;a href=&#34;https://magicall.online&#34;&gt;Magicall&lt;/a&gt;, a project we&amp;rsquo;ve been building for the past little while. It&amp;rsquo;s a video calling platform, but not like the ones you&amp;rsquo;re used to.&lt;/p&gt;
&lt;p&gt;Magicall is what happens when cryptographers get frustrated with the state of video calling. Every mainstream option falls into one of two camps: either it&amp;rsquo;s built by a trillion-dollar surveillance company that treats your conversations as training data, or it&amp;rsquo;s locked into a single ecosystem that requires everyone to own the same brand of devices.&lt;/p&gt;
&lt;p&gt;We built something different.&lt;/p&gt;
&lt;h2 id=&#34;the-problem-with-video-calling-in-2026&#34;&gt;The Problem with Video Calling in 2026&lt;/h2&gt;
&lt;p&gt;When you use Google Meet, Microsoft Teams, or most other &amp;ldquo;free&amp;rdquo; video calling services, you&amp;rsquo;re not the customer: you&amp;rsquo;re the product. Your meeting about sensitive business matters, your therapy session, your conversation with your lawyer: it all flows through infrastructure owned by companies whose primary business model is harvesting data for advertising and AI training.&lt;/p&gt;
&lt;p&gt;Even when these services claim to offer encryption, they typically mean transport encryption (SRTP)—your call is encrypted &lt;em&gt;in transit&lt;/em&gt;, but the provider can still access the content on their servers. Real end-to-end encryption, where even the service provider cannot access your communications, remains rare in mainstream video calling.&lt;/p&gt;
&lt;p&gt;FaceTime is a notable exception with genuine end-to-end encryption, but it requires everyone on the call to own an Apple device. That&amp;rsquo;s not cross-platform support; that&amp;rsquo;s vendor lock-in disguised as a feature.&lt;/p&gt;
&lt;h2 id=&#34;what-we-built&#34;&gt;What We Built&lt;/h2&gt;
&lt;p&gt;Magicall takes a different approach:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;End-to-End Encryption with Verifiable Authentication.&lt;/strong&gt; Your video, audio, and chat messages are encrypted in your browser using AES-256-GCM before they ever leave your device. But we went further: Magicall provides short authentication strings (SAS) that let you verify you&amp;rsquo;re actually talking to who you think you are, not a man-in-the-middle attacker. This is a security feature you won&amp;rsquo;t find in Google Meet, Microsoft Teams, or even FaceTime.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Zero Downloads, Zero Guest Accounts.&lt;/strong&gt; Magicall works entirely in your browser—Chrome, Firefox, Safari, Edge, desktop or mobile. When you share your room link, the recipient clicks it and joins. No app installation. No account creation. No friction.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Permanent Room URLs.&lt;/strong&gt; Every Magicall user gets a permanent room URL like &lt;code&gt;magicall.online/r/yourname&lt;/code&gt;. Put it in your email signature, your social media bio, your business card. It&amp;rsquo;s always ready. Unlike other platforms that generate random meeting links that expire, your Magicall room is yours for as long as you have an account.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;No Ads, No AI Training, No Dark Patterns.&lt;/strong&gt; We don&amp;rsquo;t show advertisements. We don&amp;rsquo;t train AI models on your conversations. We don&amp;rsquo;t use manipulative UI patterns to trick you into upgrading. Our business model is simple: we offer a genuinely useful free tier and a paid tier for users who need more.&lt;/p&gt;
&lt;h2 id=&#34;the-technical-architecture&#34;&gt;The Technical Architecture&lt;/h2&gt;
&lt;p&gt;For those who care about the implementation details, here&amp;rsquo;s how Magicall works under the hood.&lt;/p&gt;
&lt;h3 id=&#34;webrtc-with-modern-codec-support&#34;&gt;WebRTC with Modern Codec Support&lt;/h3&gt;
&lt;p&gt;Magicall is built on WebRTC, the open standard for real-time communication in browsers. Our Selective Forwarding Unit (SFU) architecture supports the latest video codecs in order of efficiency:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;AV1&lt;/strong&gt; — The newest and most efficient codec, offering excellent compression with minimal bitrate. Where hardware support exists, this delivers the best quality-to-bandwidth ratio.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;VP9&lt;/strong&gt; — Excellent compression and widely supported, especially on desktop browsers with hardware acceleration.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;H.264&lt;/strong&gt; — Universal hardware support across all devices. We use the Constrained Baseline profile for maximum compatibility.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;VP8&lt;/strong&gt; — Our fallback codec for older browsers that don&amp;rsquo;t support newer options.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For audio, we use Opus at 48kHz with 2 channels—the gold standard for voice and music transmission over the internet.&lt;/p&gt;
&lt;h3 id=&#34;end-to-end-encryption-with-sframe&#34;&gt;End-to-End Encryption with SFrame&lt;/h3&gt;
&lt;p&gt;WebRTC provides transport encryption (DTLS-SRTP) by default, which does offer true end-to-end encryption in ideal peer-to-peer scenarios where browsers connect directly. However, in the real world, most WebRTC connections can&amp;rsquo;t establish direct peer-to-peer links due to NATs and firewalls. This means traffic typically flows through TURN relay servers or, in our case, an SFU (Selective Forwarding Unit) for efficient multi-party calls. In these scenarios, DTLS-SRTP only protects data in transit between your browser and the server—the server itself can access the unencrypted media. For true end-to-end encryption—where even our SFU cannot access your media—we implement &lt;a href=&#34;https://datatracker.ietf.org/doc/html/rfc9605&#34;&gt;SFrame (Secure Frame)&lt;/a&gt;, the IETF standard for encrypting media frames in real-time communications.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s how it works:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Key Exchange.&lt;/strong&gt; When you join a call, your browser generates ephemeral encryption keys using ECDH (Elliptic Curve Diffie-Hellman) on P-256. We originally meant to go with Curve25519, but were constrained to P-256 since only NIST curves are supported by the Web Crypto API. These keys are exchanged directly between participants, never touching our servers in plaintext.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Frame Encryption.&lt;/strong&gt; Before each video or audio frame leaves your browser, it&amp;rsquo;s encrypted using AES-256-GCM with keys derived from the shared secret. The SFU receives only ciphertext—it can route packets between participants but cannot decrypt the content.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Ratcheting.&lt;/strong&gt; Keys are ratcheted forward regularly and whenever participants join or leave, providing forward secrecy. If a key is somehow compromised, it cannot decrypt past conversations.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This architecture means our servers are cryptographically blind to your call content. We handle the routing, but we can&amp;rsquo;t see or hear anything.&lt;/p&gt;
&lt;h3 id=&#34;short-authentication-strings-sas-for-verification&#34;&gt;Short Authentication Strings (SAS) for Verification&lt;/h3&gt;
&lt;p&gt;End-to-end encryption is only as strong as your confidence that you&amp;rsquo;re talking to the right person. A sophisticated attacker could potentially intercept the key exchange and perform a man-in-the-middle attack, presenting different keys to each participant while secretly relaying (and reading) all traffic.&lt;/p&gt;
&lt;p&gt;Magicall defeats this with Short Authentication Strings (SAS), similar to the approach used in the ZRTP protocol:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Hash Commitment.&lt;/strong&gt; During the key exchange, both parties commit to their public keys using cryptographic hashes before revealing them, preventing either party from adapting their keys based on the other&amp;rsquo;s.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;SAS Generation.&lt;/strong&gt; After the encrypted channel is established, both clients independently derive a short authentication string (displayed as a 2-word phrase) from the shared cryptographic state.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Verbal Verification.&lt;/strong&gt; Participants can read these strings aloud to each other. If the strings match, you have cryptographic proof that no man-in-the-middle attack is occurring—the keys you&amp;rsquo;re using are the same keys your conversation partner is using.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is a security feature absent from Google Meet, Microsoft Teams, and even FaceTime. Those platforms ask you to trust their infrastructure implicitly. Magicall lets you verify the security of your call yourself, using mathematics rather than corporate promises.&lt;/p&gt;
&lt;p&gt;For high-stakes conversations—legal consultations, medical discussions, business negotiations—this verification step takes ten seconds and provides guarantees that no amount of corporate trust policies can match.&lt;/p&gt;
&lt;h2 id=&#34;privacy-by-architecture&#34;&gt;Privacy by Architecture&lt;/h2&gt;
&lt;p&gt;We don&amp;rsquo;t record your calls or store call transcripts. Your conversations exist only in the moment, between the participants, and nowhere else.&lt;/p&gt;
&lt;p&gt;We don&amp;rsquo;t analyze your conversations for advertising purposes, and we don&amp;rsquo;t train AI models on your data. Your words aren&amp;rsquo;t fuel for someone else&amp;rsquo;s machine learning pipeline.&lt;/p&gt;
&lt;p&gt;We don&amp;rsquo;t sell or share your data with third parties. Full stop.
This isn&amp;rsquo;t a policy decision that could change with new management or shareholders—it&amp;rsquo;s an architectural decision. With end-to-end encryption, we &lt;em&gt;can&amp;rsquo;t&lt;/em&gt; access your call content even if we wanted to. The cryptographic keys are generated in your browser and never transmitted to our servers.&lt;/p&gt;
&lt;h2 id=&#34;made-in-europe-gdpr-by-default&#34;&gt;Made in Europe, GDPR by Default&lt;/h2&gt;
&lt;p&gt;Symbolic Software is based in Paris, France. Our servers are hosted in the European Union. We&amp;rsquo;re fully GDPR compliant—not as a checkbox exercise, but because we believe privacy is a fundamental right.&lt;/p&gt;
&lt;p&gt;There are no surprise data transfers to jurisdictions with weaker privacy protections. Your data stays in the EU, processed by an EU company, under EU law.&lt;/p&gt;
&lt;h2 id=&#34;why-you-should-trust-us&#34;&gt;Why You Should Trust Us&lt;/h2&gt;
&lt;p&gt;This is a fair question. Why should you trust a video calling service from a company you might not have heard of?&lt;/p&gt;
&lt;p&gt;Symbolic Software has spent nearly a decade building our reputation in applied cryptography. We&amp;rsquo;ve conducted over 250 security audits for organizations including Coinbase, Mozilla, 1Password, Bitwarden, NordVPN, ExpressVPN, Zoom, and Dashlane. Our founder has a PhD in formal verification of cryptographic protocols from Inria Paris, and has published research at IEEE S&amp;amp;P, USENIX Security, and other top academic venues.&lt;/p&gt;
&lt;p&gt;We built &lt;a href=&#34;https://verifpal.com&#34;&gt;Verifpal&lt;/a&gt;, a widely-used tool for formal verification of cryptographic protocols. We built &lt;a href=&#34;https://noiseexplorer.com&#34;&gt;Noise Explorer&lt;/a&gt;, which analyzes implementations of the Noise Protocol Framework. We built &lt;a href=&#34;https://github.com/symbolicsoft/kyber-k2so&#34;&gt;Kyber-K2SO&lt;/a&gt;, a clean implementation of the post-quantum key encapsulation mechanism that won the NIST competition.&lt;/p&gt;
&lt;p&gt;When we say Magicall is built by cryptographers, we mean it. This is what we do.&lt;/p&gt;
&lt;h2 id=&#34;pricing&#34;&gt;Pricing&lt;/h2&gt;
&lt;p&gt;Magicall has two tiers:&lt;/p&gt;
&lt;h3 id=&#34;free-forever&#34;&gt;Free (Forever)&lt;/h3&gt;
&lt;p&gt;The free tier gives you everything you need for secure video calling: end-to-end encrypted calls with up to 5 participants, 30-minute meetings that you can restart anytime, and a permanent room URL that&amp;rsquo;s yours to keep. You also get screen sharing, in-call chat, and waiting room controls to manage who joins your calls. No credit card required.&lt;/p&gt;
&lt;h3 id=&#34;pro-499month&#34;&gt;Pro ($4.99/month)&lt;/h3&gt;
&lt;p&gt;The Pro tier includes everything in Free, plus expanded capabilities for teams and power users. You can host calls with up to 256 participants and run meetings of unlimited duration. You get 5 room names instead of one, along with priority support. Custom branding and recording features are coming soon.&lt;/p&gt;
&lt;p&gt;The free tier isn&amp;rsquo;t a trial. It&amp;rsquo;s not a limited-time offer. We believe everyone deserves access to secure, encrypted communication. The free tier has no time limit on the account, no credit card required, no strings attached.&lt;/p&gt;
&lt;p&gt;30 minutes is plenty for daily standups, quick syncs, and 1-on-1s. If you need longer meetings or more participants, the Pro tier is there for you. If you don&amp;rsquo;t, the free tier will serve you indefinitely.&lt;/p&gt;
&lt;h2 id=&#34;alpha-launch&#34;&gt;Alpha Launch&lt;/h2&gt;
&lt;p&gt;We&amp;rsquo;re calling this an alpha launch because that&amp;rsquo;s exactly what it is. Magicall works—we&amp;rsquo;ve been dogfooding it for months—but more users means more edge cases, and we&amp;rsquo;d rather be honest about that upfront. Found a bug? &lt;a href=&#34;mailto:hello@magicall.online&#34;&gt;Tell us&lt;/a&gt;. Want a feature? &lt;a href=&#34;mailto:hello@magicall.online&#34;&gt;We&amp;rsquo;re listening&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;try-it-today&#34;&gt;Try It Today&lt;/h2&gt;
&lt;p&gt;Claiming your room takes 30 seconds:&lt;/p&gt;
&lt;p&gt;Your conversations are yours. They shouldn&amp;rsquo;t be training data for someone else&amp;rsquo;s AI. They shouldn&amp;rsquo;t be analyzed to show you better ads. They shouldn&amp;rsquo;t require everyone to own the same brand of devices.&lt;/p&gt;
&lt;p&gt;We built Magicall because we believe video calling can be both simple and secure. Now it&amp;rsquo;s time to find out if others agree.&lt;/p&gt;
&lt;h4 id=&#34;-try-magicall-at-magicallonline&#34;&gt;&lt;strong&gt;&lt;a href=&#34;https://magicall.online&#34;&gt;→ Try Magicall at magicall.online&lt;/a&gt;&lt;/strong&gt;&lt;/h4&gt;
</content:encoded>
      <category>Software</category>
      <category>Magicall</category>
      <category>Protocol Design</category>
      
    </item>
    
    <item>
      <title>Kyber-K2SO 1.0: Now Implementing ML-KEM</title>
      <link>https://symbolic.software/blog/2025-12-21-kyberk2so10/</link>
      <pubDate>Sun, 21 Dec 2025 00:00:00 &#43;0000</pubDate>
      <guid>https://symbolic.software/blog/2025-12-21-kyberk2so10/</guid>
      <description>Kyber-K2SO version 1.0 upgrades from Kyber v3 to ML-KEM, the NIST-standardized post-quantum key encapsulation mechanism.</description>
      <content:encoded>&lt;p&gt;&lt;a href=&#34;https://github.com/symbolicsoft/kyber-k2so&#34;&gt;Kyber-K2SO&lt;/a&gt; is Symbolic Software&amp;rsquo;s clean Go implementation of the post-quantum key encapsulation mechanism that won the NIST post-quantum cryptography competition. Today, we&amp;rsquo;re releasing version 1.0, which upgrades the implementation from Kyber v3 to &lt;a href=&#34;https://csrc.nist.gov/pubs/fips/203/final&#34;&gt;ML-KEM (FIPS 203)&lt;/a&gt;, the official NIST standard finalized in August 2024.&lt;/p&gt;
&lt;h2 id=&#34;why-ml-kem&#34;&gt;Why ML-KEM?&lt;/h2&gt;
&lt;p&gt;While Kyber was the competition submission, ML-KEM is the standardized version. The two are closely related but not identical. ML-KEM incorporates refinements based on years of cryptanalysis and implementation feedback. Most importantly, ML-KEM is the version that will be widely deployed in protocols like TLS 1.3 and other security-critical applications.&lt;/p&gt;
&lt;p&gt;By upgrading to ML-KEM, Kyber-K2SO ensures interoperability with other conforming implementations and alignment with the official NIST standard.&lt;/p&gt;
&lt;h2 id=&#34;whats-new-in-version-10&#34;&gt;What&amp;rsquo;s New in Version 1.0&lt;/h2&gt;
&lt;h3 id=&#34;ml-kem-fips-203-compliance&#34;&gt;ML-KEM (FIPS 203) Compliance&lt;/h3&gt;
&lt;p&gt;The core implementation now follows the FIPS 203 specification. We&amp;rsquo;ve replaced the old Kyber v3 test vectors with the official &lt;a href=&#34;https://github.com/C2SP/CCTV/tree/main/ML-KEM&#34;&gt;ML-KEM test vectors from C2SP/CCTV&lt;/a&gt;, which are the reference intermediate test vectors for FIPS 203 compliance testing:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;TestMLKEM768Vector&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;t&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;testing&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;T&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;nx&#34;&gt;dkBytes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;hex&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;DecodeString&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;mlkem768TestVector&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;dk&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;		&lt;span class=&#34;nx&#34;&gt;t&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Fatal&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;nx&#34;&gt;cBytes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;hex&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;DecodeString&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;mlkem768TestVector&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;c&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;		&lt;span class=&#34;nx&#34;&gt;t&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Fatal&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;nx&#34;&gt;expectedK&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;hex&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;DecodeString&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;mlkem768TestVector&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;K&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;		&lt;span class=&#34;nx&#34;&gt;t&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Fatal&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;kd&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;dk&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Kyber768SKBytes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;byte&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;kd&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;c&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Kyber768CTBytes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;byte&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;nb&#34;&gt;copy&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;dk&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[:],&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;dkBytes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;nb&#34;&gt;copy&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;c&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[:],&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;cBytes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;nx&#34;&gt;K&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;KemDecrypt768&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;c&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;dk&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;		&lt;span class=&#34;nx&#34;&gt;t&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Fatal&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;subtle&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;ConstantTimeCompare&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;K&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[:],&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;expectedK&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;		&lt;span class=&#34;nx&#34;&gt;t&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Errorf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;ML-KEM-768 test vector failed\nExpected: %x\nGot: %x&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;expectedK&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;K&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[:])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;All three security levels pass their respective test vectors: ML-KEM-512, ML-KEM-768, and ML-KEM-1024.&lt;/p&gt;
&lt;h3 id=&#34;best-effort-secret-zeroization&#34;&gt;Best-Effort Secret Zeroization&lt;/h3&gt;
&lt;p&gt;Cryptographic implementations should clear sensitive data from memory as soon as it&amp;rsquo;s no longer needed. Version 1.0 introduces systematic zeroization of secrets and intermediate values throughout the KEM operations:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// byteopsZeroBytes zeroes a byte slice to clear sensitive data from memory.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;byteopsZeroBytes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;b&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[]&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;byte&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;range&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;b&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;		&lt;span class=&#34;nx&#34;&gt;b&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This function is now called after encryption and decryption operations to clear intermediate values like the message hash, key derivation inputs, and the implicit rejection key:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;KemEncrypt768&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;publicKey&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Kyber768PKBytes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;byte&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Kyber768CTBytes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;byte&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;KyberSSBytes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;byte&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;error&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;c1&#34;&gt;// ... encryption logic ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;nf&#34;&gt;byteopsZeroBytes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;d&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[:])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;nf&#34;&gt;byteopsZeroBytes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;m&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[:])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;nf&#34;&gt;byteopsZeroBytes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;krInput&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[:])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;nf&#34;&gt;byteopsZeroBytes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;kr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[:])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;ciphertextFixedLength&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;sharedSecretFixedLength&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Additionally, if random number generation fails during key generation, the partially-constructed private key is now explicitly zeroed before returning an error:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;_&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;rand&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Read&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;privateKeyFixedLength&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;skStart&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;range&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;privateKeyFixedLength&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;		&lt;span class=&#34;nx&#34;&gt;privateKeyFixedLength&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;privateKeyFixedLength&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;publicKeyFixedLength&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We call this &amp;ldquo;best-effort&amp;rdquo; because Go&amp;rsquo;s garbage collector and compiler optimizations may still leave copies of sensitive data in memory. However, explicit zeroization remains a valuable defense-in-depth measure.&lt;/p&gt;
&lt;h3 id=&#34;defense-in-depth-bounded-rejection-sampling&#34;&gt;Defense in Depth: Bounded Rejection Sampling&lt;/h3&gt;
&lt;p&gt;The matrix generation routine uses rejection sampling to uniformly sample polynomial coefficients. While the probability of needing more than one iteration is astronomically low (approximately 10^-82), version 1.0 adds an explicit iteration bound as a safety measure:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;][&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;j&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;],&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;ctr&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;indcpaRejUniform&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;buf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[:&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;504&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;],&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;504&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;paramsN&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// Retry with remaining buffer bytes if needed&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// Bound iterations as a safety measure (probability of needing &amp;gt;1 iteration is ~10^-82)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;iterations&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;ctr&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;paramsN&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;iterations&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;100&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;iterations&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;++&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;nx&#34;&gt;missing&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;ctrn&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;indcpaRejUniform&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;buf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;504&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:],&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;168&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;paramsN&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ctr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;k&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;ctr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;k&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;paramsN&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;k&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;++&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;		&lt;span class=&#34;nx&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;][&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;j&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;][&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;k&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;missing&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;k&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ctr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;nx&#34;&gt;ctr&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;+=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;ctrn&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This prevents any theoretical infinite loop scenario, even though such a scenario is practically impossible with a correctly functioning random number generator.&lt;/p&gt;
&lt;h3 id=&#34;performance-improvements&#34;&gt;Performance Improvements&lt;/h3&gt;
&lt;p&gt;Version 1.0 includes several performance optimizations that reduce heap allocations and improve efficiency:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fixed-size arrays instead of slices:&lt;/strong&gt; Internal buffers are now declared as fixed-size arrays where possible, reducing pressure on the garbage collector:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// Before&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;buf&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;make&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;([]&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;byte&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;672&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// After&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;buf&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;672&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;byte&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Pre-computed Barrett reduction constant:&lt;/strong&gt; The Barrett reduction now uses a pre-computed constant rather than computing it at runtime:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// Before&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;v&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;int16&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;int16&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(((&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;uint32&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;26&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;uint32&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;paramsQ&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;/&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;/&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;uint32&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;paramsQ&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;t&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;int16&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;int32&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;v&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;int32&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;a&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;26&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// After&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;t&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;int16&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;((&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;int32&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;paramsBarrettV&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;int32&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;a&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;26&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Streamlined key construction:&lt;/strong&gt; Private key assembly now uses direct copy operations into the fixed-length output array, eliminating intermediate slice allocations:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// Before&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;privateKey&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;append&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;indcpaPrivateKey&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;indcpaPublicKey&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;...&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;privateKey&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;append&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;privateKey&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;pkh&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[:]&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;...&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;privateKey&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;append&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;privateKey&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;rnd&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;...&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;copy&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;privateKeyFixedLength&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[:],&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;privateKey&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// After&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;skStart&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;copy&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;privateKeyFixedLength&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[:],&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;indcpaPrivateKey&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;skStart&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;+=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;copy&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;privateKeyFixedLength&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;skStart&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:],&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;indcpaPublicKey&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;skStart&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;+=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;copy&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;privateKeyFixedLength&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;skStart&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:],&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;pkh&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[:])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;_&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;rand&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Read&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;privateKeyFixedLength&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;skStart&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;These changes result in measurably faster operations across all security levels.&lt;/p&gt;
&lt;h3 id=&#34;code-quality-improvements&#34;&gt;Code Quality Improvements&lt;/h3&gt;
&lt;p&gt;The polynomial type definition has been corrected to use the semantically appropriate constant:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// Before&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;type&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;poly&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;paramsPolyBytes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int16&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// After&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;type&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;poly&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;paramsN&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int16&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Since &lt;code&gt;paramsN&lt;/code&gt; (256) represents the polynomial degree and is the actual dimension of coefficient arrays, this change makes the code more self-documenting without affecting functionality.&lt;/p&gt;
&lt;h2 id=&#34;upgrading&#34;&gt;Upgrading&lt;/h2&gt;
&lt;p&gt;To upgrade to version 1.0:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;go get -u github.com/symbolicsoft/kyber-k2so
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The API remains unchanged. If you were previously using Kyber-K2SO with Kyber v3, your code will continue to work without modification. However, the key material and ciphertexts are now ML-KEM format, which means they are not interoperable with Kyber v3 implementations.&lt;/p&gt;
&lt;h2 id=&#34;acknowledgements&#34;&gt;Acknowledgements&lt;/h2&gt;
&lt;p&gt;We thank the &lt;a href=&#34;https://github.com/C2SP/CCTV/tree/main/ML-KEM&#34;&gt;C2SP project&lt;/a&gt; for maintaining the official test vectors that made compliance testing straightforward.&lt;/p&gt;
</content:encoded>
      <category>Software</category>
      <category>Kyber-K2SO</category>
      <category>Post-Quantum</category>
      
    </item>
    
    <item>
      <title>2PC-MPC in Rust: Audit Report</title>
      <link>https://symbolic.software/blog/2024-06-04-2pcmpc/</link>
      <pubDate>Wed, 05 Jun 2024 00:00:00 &#43;0000</pubDate>
      <guid>https://symbolic.software/blog/2024-06-04-2pcmpc/</guid>
      <description>Our initial audit of the Rust implementation of dWallet Labs&#39; 2PC-MPC protocol.</description>
      <content:encoded>&lt;p&gt;&lt;strong&gt;→ &lt;a href=&#34;https://symbolic.software/pdf/dw-01.pdf&#34;&gt;Download Full Report&lt;/a&gt; (PDF)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;At Symbolic Software, we recently concluded, in close collaboration with &lt;a href=&#34;https://www.3milabs.tech&#34;&gt;3MI Labs&lt;/a&gt; an extensive audit of the &lt;a href=&#34;https://github.com/dwallet-labs/2pc-mpc&#34;&gt;2PC-MPC Rust crate&lt;/a&gt; developed by &lt;a href=&#34;https://dwalletlabs.com&#34;&gt;dWallet Labs&lt;/a&gt;. We&amp;rsquo;re excited to share our findings and offer our perspectives on the strengths and areas for improvement of this innovative protocol implementation.&lt;/p&gt;
&lt;p&gt;dWallet Labs&amp;rsquo; 2PC-MPC crate represents a practical software implementation of the &lt;a href=&#34;https://eprint.iacr.org/2024/253&#34;&gt;&amp;ldquo;2PC-MPC: Emulating Two Party ECDSA in Large-Scale MPC&amp;rdquo; protocol&lt;/a&gt;. This novel cryptographic structure enables a non-collusive and UC-secure two-party ECDSA scheme, allowing one party to be fully centralized while abstracting away the decentralization of the second party, enabling it to scale to an arbitrary number of virtual parties.&lt;/p&gt;
&lt;h3 id=&#34;audit-methodology-and-focus-areas&#34;&gt;Audit Methodology and Focus Areas&lt;/h3&gt;
&lt;p&gt;Our audit covered the following aspects:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Functional Correctness Assessment&lt;/strong&gt;: We verified that the implementation of the protocol in the 2PC-MPC crate aligns with the specifications outlined in the accompanying paper. This included ensuring that the cryptographic operations are correctly implemented and that the protocol&amp;rsquo;s security properties are maintained.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Security Assessment&lt;/strong&gt;: We evaluated the security of the 2PC-MPC crate and its underlying crates, examining them for potential vulnerabilities that could compromise the security of the protocol. This included assessing the crates for common cryptographic vulnerabilities and ensuring their adherence to secure implementation best practices.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;key-findings-and-recommendations&#34;&gt;Key Findings and Recommendations&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Functional Correctness&lt;/strong&gt;: The implementation generally aligns with the protocol specifications, with the cryptographic operations and protocol steps being correctly realized in the Rust code. However, due to the high complexity of the target, further analysis is recommended to ensure complete functional correctness.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Security&lt;/strong&gt;: We identified three main security findings of varying severity levels, including one critical issue of nonce reuse in the decentralized party presigning step.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Code Complexity&lt;/strong&gt;: The high complexity of the Rust implementation can make the codebase challenging to understand, maintain, and audit. We recommend strategies such as code simplification, comprehensive documentation, rigorous testing, and regular security audits to address this issue.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In addition, Symbolic Software provided a roadmap for future work that proposes several avenues for further exploration and development to enhance the protocol&amp;rsquo;s security, efficiency, and usability. These include an in-depth cryptographic review, cryptographic optimizations, protocol API correctness and usability analysis, state machine transition analysis, understanding the decentralized party in a protocol setting, performance and scalability testing, integration with existing systems and frameworks, and real-world applications and case studies.&lt;/p&gt;
&lt;h3 id=&#34;read-the-report&#34;&gt;Read the Report&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;→ &lt;a href=&#34;https://symbolic.software/pdf/dw-01.pdf&#34;&gt;Download Full Report&lt;/a&gt; (PDF)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Symbolic Software would like to thank Erik Takke and Tomer Ashur from 3MI Labs for their collaboration and support throughout the assessment process. We appreciate their expertise, insights, and dedication to advancing the field of secure multi-party computation. We would also like to extend our sincere thanks to Yehonathan Cohen Scaly and Dolev Mutzari of dWallet Labs for their valuable contributions and feedback on the assessment findings. Their expertise and feedback have been instrumental in enhancing the quality and accuracy of the assessment results.&lt;/p&gt;
</content:encoded>
      <category>Security</category>
      
    </item>
    
    <item>
      <title>Security Announcement: Variable Timing Issue in Kyber Code</title>
      <link>https://symbolic.software/blog/2023-12-19-kyberk2sovariabletiming/</link>
      <pubDate>Tue, 19 Dec 2023 00:00:00 &#43;0000</pubDate>
      <guid>https://symbolic.software/blog/2023-12-19-kyberk2sovariabletiming/</guid>
      <description>We&#39;ve updated Kyber-K2SO to address a variable timing logic issue.</description>
      <content:encoded>&lt;p&gt;&lt;em&gt;&lt;strong&gt;Update (December 30, 2023):&lt;/strong&gt; Additional potential variable timing issues were &lt;a href=&#34;https://groups.google.com/a/list.nist.gov/g/pqc-forum/c/ldX0ThYJuBo&#34;&gt;found&lt;/a&gt; and subsequently &lt;a href=&#34;https://github.com/symbolicsoft/kyber-k2so/commit/2d16efee71ae195a6aef2fb36f5ed60768d78c98&#34;&gt;fixed&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/symbolicsoft/kyber-k2so&#34;&gt;Kyber-K2SO&lt;/a&gt; is Symbolic Software&amp;rsquo;s clean implementation of the &lt;a href=&#34;https://pq-crystals.org/kyber/&#34;&gt;Kyber&lt;/a&gt; IND-CCA2-secure key encapsulation mechanism (KEM), whose security is based on the hardness of solving the learning-with-errors (LWE) problem over module lattices. Kyber was recently chosen as the winning candidate algorithm of those submitted to the NIST post-quantum cryptography project.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ve updated Kyber-K2SO to address a variable timing logic vulnerability. This vulnerability also existed in the original Kyber reference implementation and continues to exist in many third-party implementations.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We&amp;rsquo;re proud to be among the first third-party implementations to patch the issue, and the first vendor to issue an actual security advisory.&lt;/strong&gt; Our implemented fix is &lt;a href=&#34;https://github.com/symbolicsoft/kyber-k2so/pull/9&#34;&gt;available for review&lt;/a&gt; on the Kyber-K2SO GitHub repository.&lt;/p&gt;
&lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt;
&lt;p&gt;The vulnerability was discovered by the &lt;a href=&#34;https://cryspen.com&#34;&gt;Cryspen&lt;/a&gt; team two weeks ago and reported directly to the Kyber team, whereupon it was &lt;a href=&#34;https://github.com/pq-crystals/kyber/commit/dda29cc63af721981ee2c831cf00822e69be3220&#34;&gt;patched&lt;/a&gt; in the official Kyber reference implementation.&lt;/p&gt;
&lt;p&gt;Despite fixing the issue, the Kyber team chose not to release an advisory. This lack of an advisory from the Kyber team meant that third-party implementations such as ours were not made aware of the vulnerability&amp;rsquo;s existence.&lt;/p&gt;
&lt;p&gt;On December 16, roughly two weeks later, we were made aware of the vulnerability&amp;rsquo;s existence through &lt;a href=&#34;https://github.com/symbolicsoft/kyber-k2so/issues/7&#34;&gt;a GitHub issue&lt;/a&gt; opened in the Kyber-K2SO repository by the Cryspen team. On December 17, we additionally received an independent email notification from Prof. Daniel J. Bernstein (who had later independently rediscovered the issue) notifying us of the vulnerability.&lt;/p&gt;
&lt;p&gt;We then issued an update containing a software patch within 24 hours. Users are asked to update to &lt;a href=&#34;https://github.com/symbolicsoft/kyber-k2so/releases&#34;&gt;Kyber-K2SO version 0.2.2&lt;/a&gt; (or higher) to benefit from our fix for this issue.&lt;/p&gt;
&lt;h2 id=&#34;the-vulnerability&#34;&gt;The Vulnerability&lt;/h2&gt;
&lt;p&gt;The variable timing logic arises from how the original implementation divides by Q in the decapsulation function, as seen below in Kyber-K2SO&amp;rsquo;s original code:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// polyToMsg converts a polynomial to a 32-byte message&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// and represents the inverse of polyFromMsg.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;polyToMsg&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;a&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;poly&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[]&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;byte&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;msg&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;make&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;([]&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;byte&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;paramsSymBytes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kd&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;t&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;uint16&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;a&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;polyCSubQ&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;a&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;paramsN&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;/&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;8&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;++&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;msg&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;j&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;j&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;8&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;j&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;++&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;c1&#34;&gt;// NOTE: the following assignment is responsible&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;c1&#34;&gt;// for the variable timing issue, through the&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;c1&#34;&gt;// division by paramsQ.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nx&#34;&gt;t&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;p&#34;&gt;((&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;uint16&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;a&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;8&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;+&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;j&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;])&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;uint16&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;paramsQ&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;/&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;o&#34;&gt;/&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;uint16&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;paramsQ&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nx&#34;&gt;msg&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;|=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;byte&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;t&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;j&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;msg&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;As seen below, this vulnerabilty was also present in the &lt;a href=&#34;https://github.com/pq-crystals/kyber/commit/dda29cc63af721981ee2c831cf00822e69be3220&#34;&gt;reference C Kyber code&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;KYBER_N&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;/&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;8&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;++&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;msg&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;for&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;j&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;j&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;8&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;j&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;++&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;t&lt;/span&gt;  &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;a&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;coeffs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;8&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;+&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;j&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;t&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;+=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;((&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int16_t&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;t&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;15&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;KYBER_Q&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;t&lt;/span&gt;  &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(((&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;t&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;KYBER_Q&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;/&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;/&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;KYBER_Q&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;// Irrelevant code snipped
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It is unclear whether this division will be compiled into a multiplication instruction, depending on compiler optimizations. &lt;a href=&#34;https://github.com/symbolicsoft/kyber-k2so/issues/7&#34;&gt;As noted by Goutam Tamvada&lt;/a&gt;, we can see that the output of some C compilers indeed produces a division instruction even when strong optimizations are specified:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://symbolic.software/images/blog/kyber-timing-1.png&#34; alt=&#34;Compilation result: x64 msvc v19.latest&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://symbolic.software/images/blog/kyber-timing-2.png&#34; alt=&#34;Compilation result: RISC-V (32-bit) gcc 13.2.0&#34;&gt;&lt;/p&gt;
&lt;p&gt;Should a division instruction be emitted, its execution time would likely be variable and leak information about its secret input.&lt;/p&gt;
&lt;h2 id=&#34;most-implementations-affected&#34;&gt;Most Implementations Affected&lt;/h2&gt;
&lt;p&gt;Even if you do not use Kyber-K2SO, there is a strong chance that your chosen Kyber implementation suffers from the same variable timing vulnerability. The &lt;a href=&#34;https://kyberslash.cr.yp.to/libraries.html&#34;&gt;KyberSlash&lt;/a&gt; website is currently tracking which libraries are vulnerable and which have issued patches or security advisories.&lt;/p&gt;
&lt;h2 id=&#34;impact&#34;&gt;Impact&lt;/h2&gt;
&lt;p&gt;As far as we can tell, the impact of this vulnerability is the same as any standard variable timing vulnerability in low-level asymmetric cryptographic primitives: real-world impact can vary dramatically depending on practical use cases. However, in the worst case scenario, secret key information leakage could be possible. Therefore, we encourage everyone to examine their chosen Kyber implementations to ensure that they are protected from this issue.&lt;/p&gt;
&lt;h2 id=&#34;acknowledgements&#34;&gt;Acknowledgements&lt;/h2&gt;
&lt;p&gt;We thank Goutam Tamvada, Karthikeyan Bhargavan, Franziskus Kiefer, Prof. Daniel J. Bernstein and Prof. Peter Schwabe for their valuable feedback regarding this issue.&lt;/p&gt;
</content:encoded>
      <category>Security</category>
      <category>Kyber-K2SO</category>
      <category>Post-Quantum</category>
      <category>Side Channels</category>
      
    </item>
    
    <item>
      <title>Supporting Real World Crypto 2024</title>
      <link>https://symbolic.software/blog/2023-11-01-rwc2024/</link>
      <pubDate>Wed, 01 Nov 2023 00:00:00 &#43;0000</pubDate>
      <guid>https://symbolic.software/blog/2023-11-01-rwc2024/</guid>
      <description>Symbolic Software continues its support for the IACR Real World Cryptography Symposium.</description>
      <content:encoded>&lt;p&gt;We are pleased to announce that Symbolic Software is once again sponsoring the &lt;a href=&#34;https://rwc.iacr.org&#34;&gt;IACR Real World Cryptography symposium for its 2024 edition&lt;/a&gt;. This marks the sixth consecutive year of our association with this esteemed event, and we couldn&amp;rsquo;t be prouder.&lt;/p&gt;
&lt;p&gt;The IACR Real World Cryptography symposium is a significant event in the cryptography community. It brings together researchers, practitioners, and industry experts to discuss the latest advancements, challenges, and practical applications of cryptographic techniques in the real world. Over the years, the symposium has been a platform for groundbreaking research, fostering collaborations, and promoting the importance of cryptography in ensuring a safer digital world.&lt;/p&gt;
&lt;p&gt;Through supporting the IACR Real World Cryptography symposium, we hope to concretely demonstrate Symbolic Software&amp;rsquo;s commitment to advancing the field of cryptography. We believe in the power of collaboration and the importance of bridging the gap between theory and practice. By supporting this symposium, we aim to contribute to the growth of the cryptographic community and the broader goal of enhancing digital security.&lt;/p&gt;
&lt;p&gt;We would like to extend our gratitude to the organizers of the IACR Real World Cryptography symposium for their tireless efforts in making this event a success year after year. We also want to acknowledge the participants, speakers, and attendees who bring their expertise, insights, and enthusiasm, making the symposium a vibrant and enriching experience.&lt;/p&gt;
&lt;p&gt;As we look forward to the 2024 edition, we invite everyone to join us in celebrating the advancements in real-world cryptography. Let&amp;rsquo;s continue to work together, share knowledge, and drive innovation in this crucial field.&lt;/p&gt;
&lt;p&gt;Thank you for your continued support and trust in Symbolic Software. We hope to see you at the IACR Real World Cryptography symposium in 2024!&lt;/p&gt;
</content:encoded>
      <category>Announcement</category>
      
    </item>
    
    <item>
      <title>Address to French Law Enforcement Officials</title>
      <link>https://symbolic.software/blog/2023-09-29-cnam/</link>
      <pubDate>Fri, 29 Sep 2023 00:00:00 &#43;0000</pubDate>
      <guid>https://symbolic.software/blog/2023-09-29-cnam/</guid>
      <description>An address to France&#39;s law enforcement officials on Internet surveillance policy in France.</description>
      <content:encoded>&lt;p&gt;In a recent address to top French law enforcement officials, directors, ambassadors, and foreign dignitaries, Nadim Kobeissi, the director of Symbolic Software, a French cryptography expertise company, delved deep into the pressing topic of Internet surveillance law in France. The speech, which we are pleased to share in its entirety below, critically examines the current trajectory of French legislative proposals concerning digital privacy, encryption, and cybersecurity.&lt;/p&gt;
&lt;p&gt;The speech aimed to use technical expertise in order to challenge the prevailing narratives and highlight the potential pitfalls of certain regulatory approaches. From the fundamental right to encryption to the potential dangers of turning smartphones into surveillance tools, the speech offers a comprehensive overview of the challenges and opportunities presented by the digital age.&lt;/p&gt;
&lt;p&gt;As you read, we invite you to reflect on the balance between security and individual rights, the importance of informed legislation, and the broader implications of the choices we make today for the future of the digital world in France.&lt;/p&gt;
&lt;p&gt;The speech is available below in its original French:&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Au cours des dernières années, nous avons assisté à une évolution majeure de l&amp;rsquo;approche réglementaire de l&amp;rsquo;Union européenne concernant l&amp;rsquo;Internet et les technologies personnelles. De nombreuses propositions ont vu le jour, certaines d&amp;rsquo;entre elles suscitant de vives inquiétudes quant à leur applicabilité, leur efficacité et leur respect des droits fondamentaux.&lt;/p&gt;
&lt;p&gt;Je suis ici pour souligner qu&amp;rsquo;une grande partie de l&amp;rsquo;élan qui sous-tend cette législation est mal informée. Ce n&amp;rsquo;est pas parce que je veux moins de réglementation, parce que je déteste les règles, ou quoi que ce soit de ce genre. C&amp;rsquo;est simplement parce que, du point de vue d&amp;rsquo;un expert technique en sécurité numerique et en cryptographie, la France essaie de réglementer l&amp;rsquo;ère numérique d&amp;rsquo;une manière qui me fait penser qu&amp;rsquo;elle réglementerait également les voitures en excès de vitesse en exigeant que toutes les roues soient dorenavant en béton.&lt;/p&gt;
&lt;p&gt;Mais plus que tout, je tiens à souligner l&amp;rsquo;importance cruciale d&amp;rsquo;élargir le débat : je ne suis pas ici pour critiquer juste pour critiquer. Je ne suis pas là pour faire un récital de poésie sur les &amp;ldquo;droits de l&amp;rsquo;homme&amp;rdquo; sans tenir compte des situations graves et souvent très laides auxquelles des professionnels comme par exemple monsieur le général de la gendarmerie doivent faire face en permanence. Je suis ici parce que l&amp;rsquo;establishment français aborde parfois certains sujets profondément importants avec ce que l&amp;rsquo;on peut, en toute justice, décrire comme des réactions de panique ou une simple paresse intellectuelle, et qui, par conséquent, ne font qu&amp;rsquo;affaiblir la confiance du public dans les institutions de l&amp;rsquo;État, tout en ne contribuant guère à résoudre les questions de sécurité et d&amp;rsquo;ordre public.&lt;/p&gt;
&lt;h3 id=&#34;droit-au-chiffrement&#34;&gt;Droit au chiffrement&lt;/h3&gt;
&lt;p&gt;Le chiffrement est ajourd&amp;rsquo;hui un droit fondamental. Il est essentiel pour garantir la confidentialité des communications et protéger les citoyens contre la surveillance de masse. Chiffrer ses communications est une pratique courante qui garantit que nos échanges ne soient lus que par leurs destinataires légitimes. C&amp;rsquo;est une extension de notre droit à la vie privée, protégé par l&amp;rsquo;article 8 de la Convention européenne des droits de l&amp;rsquo;homme. Cette pratique est adoptée par diverses professions, des militants aux journalistes, en passant par les avocats et les médecins, et est popularisée par des applications comme WhatsApp ou Signal.&lt;/p&gt;
&lt;p&gt;Cependant, dans une affaire recente dans laquelle 7 personnes ont été mises en examen pour « association de malfaiteurs terroristes » en décembre 2020, La DGSI a publié un communiqué dans lequel elle attribue l&amp;rsquo;utilisation de Tor, une technologie de navigation privée sur Internet, comme étant un signe de &amp;ldquo;comportements clandestins&amp;rdquo;. Un juge chargé de l&amp;rsquo;affaire a en outre déclaré que l&amp;rsquo;utilisation de l&amp;rsquo;application de messagerie populaire Signal, ainsi que l&amp;rsquo;utilisation d&amp;rsquo;ordinateurs portables ou de téléphones chiffrés, constituaient des motifs de comportement suspect et potentiellement criminel.&lt;/p&gt;
&lt;p&gt;Après leurs arrestations, les mis en examen sont, comme noté par La Quadrature du Net, systématiquement questionnés sur leur utilisation des outils de chiffrement et sommés de se justifier : « Utilisez-vous des messageries cryptées comme WhatsApp, Signal, Telegram, ou ProtonMail? », « Pour vos données personnelles, utilisez-vous un système de chiffrement ? », « Pourquoi utilisez-vous ce genre d’applications de chiffrement et d’anonymisation sur internet ? ». Le lien supposé entre chiffrement et criminalité est clair: au total, on dénombre plus de 150 questions liées aux pratiques numériques.&lt;/p&gt;
&lt;p&gt;Il est extrêmement important de noter ce qui suit :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Signal est une application de messagerie qui compte des millions d&amp;rsquo;utilisateurs dans le monde et qui a été largement financée par les fondateurs de WhatsApp. La technologie de chiffrement de Signal est exactement la même que celle utilisée par WhatsApp. Des milliards de personnes utilisent quotidiennement Signal et WhatsApp.&lt;/li&gt;
&lt;li&gt;Tous les iPhone, téléphones Samsung et téléphones Google chiffrent par défaut toutes les données stockées localement. Tous les ordinateurs portables Mac sont livrés avec une fonction de chiffrement prête à l&amp;rsquo;emploi, appelée FileVault, et tous les ordinateurs portables Windows de qualité professionnelle sont livrés avec une fonction de chiffrement de disque équivalente, appelée BitLocker.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Il est très important que tout le monde ici se rende compte que ce genre de réflexion, de la part des législateurs, ne fait que signaler aux citoyens français que les institutions gouvernementales sont incompétentes et qu&amp;rsquo;elles ne sont pas dignes de confiance. Aucun criminel n&amp;rsquo;est arrêté de cette manière, aucune sécurité n&amp;rsquo;est appliquée de cette manière. C&amp;rsquo;est un spectacle qui réduit la confiance du public dans les institutions que beaucoup d&amp;rsquo;entre vous représentent ici, et qui ne présente aucun avantage. Ces actions semblent suggérer que la protection de la vie privée est synonyme de comportement suspect ou clandestin. Cette perspective est non seulement erronée, mais elle est également dangereuse, car elle risque de dissuader les citoyens d&amp;rsquo;utiliser des outils essentiels pour protéger leurs données personnelles.&lt;/p&gt;
&lt;p&gt;Le plus ironique (et le plus drôle), c&amp;rsquo;est que la plupart des cybercriminels n&amp;rsquo;ont pas vraiment besoin de se préoccuper d’utiliser le chiffrement pour commencer ! Une grande partie de la cybercriminalité en France aujourd&amp;rsquo;hui est en fait réalisée par le biais de choses comme ces faux SMS que tout le monde ici a sûrement reçus, qui vous demandent de payer une fausse amende ou de suivre un faux colis Amazon. Beaucoup de ces SMS proviennent de centres de données en Moldavie, et certains pourraient en fait faire partie d&amp;rsquo;initiatives parrainées par des États étrangers pour saper la cybersécurité civile française à grande échelle.&lt;/p&gt;
&lt;h3 id=&#34;projet-de-loi-sren&#34;&gt;Projet de loi SREN&lt;/h3&gt;
&lt;p&gt;Le Projet de loi visant à sécuriser et réguler l’espace numérique (dit SREN) est un autre exemple frappant des défis auxquels nous sommes confrontés en matière de réglementation de l&amp;rsquo;Internet. Ce projet de loi, qui vise à réguler les contenus en ligne, semble ignorer la réalité complexe et nuancée de l&amp;rsquo;espace numérique. Par exemple, il est proposé d&amp;rsquo;obliger les plateformes à conserver les messages éphémères échangés, une mesure qui pourrait avoir des implications profondes pour la vie privée des utilisateurs.&lt;/p&gt;
&lt;p&gt;De plus, la proposition du projet de loi SREN d&amp;rsquo;intégrer un code de censure directement dans les navigateurs web est particulièrement alarmante. Mandater l&amp;rsquo;intégration d&amp;rsquo;un code spécifique dans tous les principaux navigateurs web est une idée qui, à première vue, peut sembler séduisante pour certains régulateurs souhaitant exercer un contrôle sur l&amp;rsquo;espace numérique. Cependant, une telle proposition est non seulement techniquement irréalisable, mais elle est aussi profondément problématique sur le plan éthique et pratique. Les navigateurs web, en tant que portails d&amp;rsquo;accès à l&amp;rsquo;Internet, sont conçus pour être neutres, offrant aux utilisateurs la liberté de naviguer et d&amp;rsquo;accéder à l&amp;rsquo;information sans entrave. Imposer un code gouvernemental dans ces navigateurs reviendrait à compromettre cette neutralité fondamentale, transformant des outils d&amp;rsquo;accès libre en instruments de surveillance ou de censure.&lt;/p&gt;
&lt;p&gt;De plus, l&amp;rsquo;idée de mandater un code dans tous les navigateurs majeurs sous-estime la diversité et la complexité de l&amp;rsquo;écosystème des navigateurs. Avec une multitude de navigateurs disponibles, allant des géants comme Google Chrome, Mozilla Firefox, Microsoft Edge ou Apple Safari à des alternatives plus spécialisées et open-source, l&amp;rsquo;application uniforme d&amp;rsquo;un tel mandat serait un cauchemar logistique. Sans parler des implications en matière de sécurité : introduire un code gouvernemental pourrait créer des vulnérabilités, exposant potentiellement des millions d&amp;rsquo;utilisateurs à des risques.&lt;/p&gt;
&lt;p&gt;De plus, l&amp;rsquo;histoire nous a montré qu&amp;rsquo;il est très difficile pour une autorité centrale et établie de décider avec autorité de ce qui est une menace et de ce qui ne l&amp;rsquo;est pas. Nous avons vu ce même type d&amp;rsquo;autorité, tout récemment encore, déclarer que Signal et WhatsApp étaient des indicateurs de menace. Qui décide quel type d&amp;rsquo;expression est si menaçant que nous devons modifier tous les navigateurs web pour l&amp;rsquo;interdire purement et simplement ?&lt;/p&gt;
&lt;p&gt;L&amp;rsquo;approche actuelle du gouvernement en matière de réglementation semble être axée sur la censure et l&amp;rsquo;autoritarisme. Sur le plan éthique, une telle initiative ouvrirait la porte à un glissement dangereux. Aujourd&amp;rsquo;hui, il pourrait s&amp;rsquo;agir d&amp;rsquo;un code pour des raisons de sécurité ou de réglementation, mais demain, qu&amp;rsquo;est-ce qui empêcherait l&amp;rsquo;extension de ces mandats à des fins plus autoritaires ou restrictives ? Les responsables des institutions ici présentes me paraissent largement érudits et raisonnables ; mais le citoyen n&amp;rsquo;a aucune garantie que vos successeurs partageront le même état d&amp;rsquo;esprit. Une fois que le précédent est établi, il devient plus facile d&amp;rsquo;étendre et d&amp;rsquo;abuser de ce pouvoir, menant potentiellement à une censure accrue, à une surveillance de masse et à une érosion de la confiance des utilisateurs dans les outils numériques qu&amp;rsquo;ils utilisent au quotidien.&lt;/p&gt;
&lt;p&gt;Aucun pays, jusqu&amp;rsquo;à présent, n&amp;rsquo;a tenté d&amp;rsquo;imposer l&amp;rsquo;intégration d&amp;rsquo;un code de censure ou de surveillance contrôlé par le gouvernement dans tous les principaux navigateurs web. C&amp;rsquo;est une démarche sans précédent qui soulève des questions fondamentales sur la liberté d&amp;rsquo;expression, la vie privée et la confiance dans l&amp;rsquo;espace numérique. La France doit se demander si elle souhaite véritablement être le premier pays de l&amp;rsquo;Union européenne, voire le premier au monde, à emprunter cette voie.&lt;/p&gt;
&lt;p&gt;Il est essentiel de se rappeler que la censure, sous quelque forme que ce soit, est une atteinte à la liberté d&amp;rsquo;expression. Permettez-moi de préciser ici que je comprends la priorité que beaucoup peuvent raisonnablement accorder aux questions d&amp;rsquo;ordre social, de cohésion sociale. Ma riposte serait que les discours violents sont probablement le symptôme d&amp;rsquo;un manque d&amp;rsquo;ordre social, et non la cause. Si nous admettons que c&amp;rsquo;est le cas, c&amp;rsquo;est à la cause qu&amp;rsquo;il faut s&amp;rsquo;attaquer, et non au symptôme.&lt;/p&gt;
&lt;h3 id=&#34;surveillance-des-smartphones&#34;&gt;Surveillance des smartphones&lt;/h3&gt;
&lt;p&gt;L’article 3 du projet de loi dite “d’orientation et de programmation du ministère de la Justice”, récemment discuté en France, vise à legitimiser la transformation des smartphones en outils de surveillance, Il s&amp;rsquo;agit d&amp;rsquo;un pouvoir dont les forces de l&amp;rsquo;ordre disposent déjà en France, mais cette loi le légitime davantage, en permettant aux forces de l&amp;rsquo;ordre de transformer à distance n&amp;rsquo;importe quel smartphone en dispositif d&amp;rsquo;espionnage en prenant le contrôle de sa géolocalisation, de son appareil photo et de son microphone grâce à l&amp;rsquo;exploitation de bogues logiciels et en piratant essentiellement le téléphone.&lt;/p&gt;
&lt;p&gt;Je ne sais pas si monsieur le général de la gendarmerie est encore dans l&amp;rsquo;assistance, mais si c&amp;rsquo;est le cas, il est probablement agacé que je n&amp;rsquo;aie pas mentionné que cette capacité existe deja (meme si sous une forme moins juridiquement encadrée) , a quel point elle sera contrôlée et restreinte par les juges, ou le fait qu&amp;rsquo;elle ne sera utilisée que dans des cas de sécurité extrêmement graves, qu’elle serait autorisée seulement pour les infractions punies d’au moins cinq ans d’emprisonnement, ou encore qu&amp;rsquo;elle nécessite un mandat valable pendant 15 jours et renouvelable une seule fois. Voilà, tout est sur la table.&lt;/p&gt;
&lt;p&gt;Le problème, c&amp;rsquo;est qu&amp;rsquo;à chaque fois que nous parlons des risques de mesures comme celle-ci, nous parlons de la question d&amp;rsquo;un nouveau précédent judiciaire. Personnellement, je peux dire sincrement que j’ai eu l&amp;rsquo;impression ce matin que mon général semble etre un chef mesuré et responsable. Mais ce n&amp;rsquo;est pas lui qui me préoccupe : ce qui m&amp;rsquo;inquiète, c&amp;rsquo;est les personnes qui pourrait lui succéder dans les années à venir. Ces personnes, mesurée ou carrément autoritaire, seraient armés des nouveaux précédents judiciaires qui sont accordés grâce à ces lois. C&amp;rsquo;est, simplement, trop de pouvoir à donner a l’état en perpetuité.&lt;/p&gt;
&lt;p&gt;De plus, cette mesure pourrait avoir des conséquences imprévues en matière de sécurité. Si notre gouvernement peut activer à distance les appareils électroniques, cela signifie qu&amp;rsquo;il existe une porte dérobée ou une vulnérabilité qui permet cette action. Ces vulnérabilités pourraient être exploitées par des acteurs malveillants, tels que des pirates informatiques, pour accéder aux informations des utilisateurs ou pour commettre d&amp;rsquo;autres actes malveillants. Au lieu de renforcer la sécurité, une telle mesure pourrait la compromettre davantage. Et je suis désolé de dire que je ne crois tout simplement pas que la France dispose d&amp;rsquo;un avantage en matière de cybersécurité qui puisse empêcher, par exemple, la Chine d&amp;rsquo;exploiter ces scénarios pour espionner les citoyens français. En clair, si la France découvre une vulnérabilité de type &amp;ldquo;zero-day&amp;rdquo;, la Chine l&amp;rsquo;a probablement déjà stockée pour une semaine. Il est donc préférable de corriger autant de failles que possible, en les rendant impuissantes pour toutes les parties, plutôt que de les stocker comme des armes.&lt;/p&gt;
&lt;p&gt;Aujourd&amp;rsquo;hui, posséder un smartphone n&amp;rsquo;est plus un simple luxe ou un gadget technologique ; c&amp;rsquo;est devenu une nécessité absolue pour la survie dans notre société moderne. Ces appareils sont bien plus que de simples téléphones. Ils sont essentiels pour travailler, étudier, gérer nos finances, accéder à des services de santé, et même pour des tâches aussi basiques que faire ses courses ou utiliser les transports en commun. Dans de nombreux cas, ne pas avoir de smartphone équivaut à être coupé du monde moderne, à être désavantagé socialement, économiquement et même culturellement.&lt;/p&gt;
&lt;p&gt;Transformer les smartphones, qui sont devenus des extensions de nous-mêmes, en outils de surveillance omniprésents, c&amp;rsquo;est forcer les citoyens à choisir entre participer à la société moderne et protéger leurs droits les plus élémentaires.&lt;/p&gt;
&lt;h3 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Il est apparent que la France semble aujourd&amp;rsquo;hui s&amp;rsquo;égarer dans un labyrinthe de propositions législatives malavisées et potentiellement liberticides. L&amp;rsquo;élément le plus stupéfiant dans cette situation est qu&amp;rsquo;il existe des preuves accablantes de compétences réelles à des postes d&amp;rsquo;autorité. Puisque c&amp;rsquo;est le cas, pourquoi nous retrouvons-nous continuellement avec des propositions législatives qui semblent fondamentalement mal interpréter l&amp;rsquo;approche de la cybersécurité ? Pourquoi l&amp;rsquo;approche est-elle si inévitablement maladroite et directement contraire à la façon dont la technologie fonctionne de nos jours ?&lt;/p&gt;
&lt;p&gt;Ma position est que ces mesures ne contribuent que très peu à l&amp;rsquo;ordre social, tout en contribuant fortement à l&amp;rsquo;érosion de la confiance dans nos institutions et dans la manière dont elles choisissent de relever les défis de l&amp;rsquo;ère numérique. Il est urgent que la France change de cap. Il est temps d&amp;rsquo;ouvrir le débat, d&amp;rsquo;écouter les experts, de construire une législation efficace, respectueuse des droits fondamentaux et à la hauteur des enjeux du XXIe siècle. Si nous ne le faisons pas, nous risquons de laisser en héritage une France affaiblie, divisée et en décalage avec son temps. Et cela, aucun citoyen français ne devrait l&amp;rsquo;accepter.&lt;/p&gt;
</content:encoded>
      <category>Announcement</category>
      
    </item>
    
    <item>
      <title>Native Labs: Audit Report</title>
      <link>https://symbolic.software/blog/2023-08-30-nativelabs/</link>
      <pubDate>Mon, 28 Aug 2023 00:00:00 &#43;0000</pubDate>
      <guid>https://symbolic.software/blog/2023-08-30-nativelabs/</guid>
      <description>Our extensive audit for the Native Labs smart contracts.</description>
      <content:encoded>&lt;p&gt;At Symbolic Software, we recently concluded an extensive audit of the &lt;a href=&#34;https://native.org&#34;&gt;Native Labs&lt;/a&gt; smart contracts. We&amp;rsquo;re excited to share our findings and offer our perspectives on the strengths and areas for improvement.&lt;/p&gt;
&lt;p&gt;Native Labs offers a decentralized exchange system that focuses on operational efficiency, code quality, interoperability, on-chain and off-chain transactions, liquidity models, and user experience. Our evaluations aimed to present a holistic view of the smart contracts&amp;rsquo; functionality, efficiency, and usability.&lt;/p&gt;
&lt;h3 id=&#34;audit-methodology-and-focus-areas&#34;&gt;Audit Methodology and Focus Areas&lt;/h3&gt;
&lt;p&gt;Our audit covered the following aspects:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Performance Evaluation&lt;/strong&gt;: We assessed the operational efficiency, with emphasis on gas usage, scalability, and transaction speed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Code Quality Inspection&lt;/strong&gt;: This centered around code readability, maintainability, and best practices.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Interoperability Review&lt;/strong&gt;: We examined the integration capabilities with both internal and third-party entities.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;On-chain and Off-chain Analysis&lt;/strong&gt;: We investigated the handling of transactions, ensuring their accuracy, security, and effectiveness.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Evaluation of Liquidity Models&lt;/strong&gt;: We delved into the liquidity models provided by Native.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;User Experience Audit&lt;/strong&gt;: We gauged how the smart contracts impact the overall user experience.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;key-findings-and-recommendations&#34;&gt;Key Findings and Recommendations&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Performance&lt;/strong&gt;: The smart contracts showcased sufficient efficiency, with optimized gas consumption leading to reduced transaction costs. However, given the evolving Ethereum gas landscape, continuous monitoring and optimization are suggested.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Code Quality&lt;/strong&gt;: Despite some inconsistencies in coding style, the comprehensive documentation provided by the Native team played a pivotal role in our understanding of the codebase. We recommend the adoption of a consistent coding style and possibly leveraging a linter.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Interoperability&lt;/strong&gt;: The smart contracts demonstrated impressive interoperability capabilities, especially with significant platforms like PancakeSwap, Uniswap v3, and 1inch.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;On-chain and Off-chain Operations&lt;/strong&gt;: The system effectively handles both transaction types. We advise the team to consistently review these operations, particularly in light of potential changes in the blockchain environment.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Liquidity Models&lt;/strong&gt;: Native&amp;rsquo;s models proved efficient and user-centric, although we emphasize the need to educate users on potential risks like impermanent loss.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;User Experience&lt;/strong&gt;: The contracts excel in accessibility, transparency, and ease of use. Regular user feedback will be invaluable to further enhance the user experience.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;read-the-report&#34;&gt;Read the Report&lt;/h3&gt;
&lt;p&gt;Our comprehensive audit reveals that the Native Labs smart contracts are developed meticulously with a clear focus on user experience, performance, and security. While there are areas for improvement, the overarching impression is positive. These insights should provide users and stakeholders with a more profound understanding of the platform&amp;rsquo;s integrity and reliability.&lt;/p&gt;
&lt;p&gt;To dive deeper into our analysis and findings, you can &lt;a href=&#34;https://symbolic.software/pdf/nat-001.pdf&#34;&gt;download the full audit report&lt;/a&gt;.&lt;/p&gt;
</content:encoded>
      <category>Security</category>
      
    </item>
    
    <item>
      <title>One Year of Verifpal</title>
      <link>https://symbolic.software/blog/2020-09-02-verifpaloneyear/</link>
      <pubDate>Wed, 02 Sep 2020 00:00:00 &#43;0000</pubDate>
      <guid>https://symbolic.software/blog/2020-09-02-verifpaloneyear/</guid>
      <description>Understanding Verifpal&#39;s relationship with cryptographic protocol security.</description>
      <content:encoded>&lt;p&gt;Last week, Verifpal, our cryptographic protocol modeling and analysis framework, celebrated its one-year anniversary. In its first year, Verifpal has made some significant achievements.&lt;/p&gt;
&lt;h3 id=&#34;improvements-to-usability-features-and-reliability&#34;&gt;Improvements to Usability, Features, and Reliability&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;First, Verifpal has improved its relationship with users.&lt;/strong&gt; Verifpal was &lt;a href=&#34;https://nlnet.nl/news/2020/20200611-SecureVideochatVerifpal.html&#34;&gt;part of Zoom&amp;rsquo;s effort&lt;/a&gt; to modernize its cryptography. &lt;a href=&#34;https://www.youtube.com/watch?v=it_hJkVU-UA&#34;&gt;Verifpal for Visual Studio Code&lt;/a&gt; and &lt;a href=&#34;https://verifhub.verifpal.com&#34;&gt;VerifHub&lt;/a&gt; allowed engineers to model and collaborate on cryptographic protocols more easily than ever. And &lt;a href=&#34;https://twitter.com/verifpal/status/1285662686894850050&#34;&gt;better attack traces&lt;/a&gt; allowed for easier to understand analysis results.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://pbs.twimg.com/media/EWtWLhcXsAEwUpV?format=jpg&amp;amp;name=4096x4096&#34; alt=&#34;Verifpal for Visual Studio Code allows visualizing and analyzing Verifpal models straight inside Visual Studio Code, with easy access to documentation for primitives and other functionality by hovering with the cursor.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Verifpal for Visual Studio Code allows visualizing and analyzing Verifpal models straight inside Visual Studio Code, with easy access to documentation for primitives and other functionality by hovering with the cursor.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Second, Verifpal obtained new features that allow it to capture more of a protocol&amp;rsquo;s workings.&lt;/strong&gt; Verifpal gained the ability to model protocols where users can potentially input &lt;strong&gt;weak or strong passwords&lt;/strong&gt;. This is very useful for modeling protocols that include user-provided passwords at points, and is a great way to separate guessable passwords from material that can actually securely be used as an encryption key of some kind. &lt;strong&gt;New primitives&lt;/strong&gt; were introduced, allowing the usage of &lt;a href=&#34;https://source.symbolic.software/verifpal/verifpal/-/commit/fbc4d7372c0fe7df484d7331f045ff5710a63d37&#34;&gt;ring signatures&lt;/a&gt;, public key encryption, &lt;a href=&#34;https://source.symbolic.software/verifpal/verifpal/-/commit/7467e8abb274778a1b668bed46d5ee67b8574478&#34;&gt;Shamir secret sharing&lt;/a&gt; and &lt;a href=&#34;https://source.symbolic.software/verifpal/verifpal/-/commit/d2261c6b0daddbdccee9ef72e06604ede463d405&#34;&gt;blind signatures&lt;/a&gt;. The &lt;em&gt;leaks&lt;/em&gt; keyword now exists, allowing principals to leak constants to the attacker without necessarily sending them to another principal. &lt;strong&gt;&lt;a href=&#34;https://source.symbolic.software/verifpal/verifpal/-/commit/221b85db613c6634069f323f84d21c89454db6db&#34;&gt;Phases&lt;/a&gt;&lt;/strong&gt; are an exciting new feature that allows Verifpal to reliably model post-compromise security properties such as forward secrecy or future secrecy.  &lt;strong&gt;&lt;a href=&#34;https://source.symbolic.software/verifpal/verifpal/-/commit/780dc774226390eb8183c7ccfa61ed83a9d26e56&#34;&gt;Query preconditions&lt;/a&gt;&lt;/strong&gt; allow for illustration relationships between different Verifpal queries on a protocol. And dozens of small improvements and changes were made to Verifpal across its application stack.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Finally, and most importantly, Verifpal improved its analysis strength and reliability.&lt;/strong&gt; As documented in a previous post, Verifpal&amp;rsquo;s formal semantics and passive attacker analysis are now &lt;a href=&#34;https://blog.symbolic.software/2020/04/14/freshness-unlinkability-model-semantics-verifpal/&#34;&gt;fully modeled in the Coq theorem prover&lt;/a&gt;. While this does not include active attacker analysis methodology, it does indicate that we can capture Verifpal&amp;rsquo;s semantics elegantly in existing formalism methods as well as a basic component of its analysis methodology. Our &lt;a href=&#34;https://verifpal.com/paper&#34;&gt;revised Verifpal paper&lt;/a&gt; goes into further detail on the Verifpal active attacker analysis methodology and describes formally how primitives are defined, deconstructed and analyzed in Verifpal. Furthermore, valuable feedback was received from over a dozen Verifpal community members that allowed detecting and fixing analysis errors, misleading analysis results and that pushed us to more concretely define the meaning and function of Verifpal queries such as freshness and authentication. These discussions occurred on the &lt;a href=&#34;https://verifpal.com/list&#34;&gt;Verifpal Mailing List&lt;/a&gt;, the &lt;a href=&#34;https://verifpal.com/discord&#34;&gt;Verifpal Discord&lt;/a&gt; as well as via private feedback.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://symbolic.software/images/blog/image.webp&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;Verifpal&amp;rsquo;s paper was revised to go into further detail regarding the analysis methodology, formal semantics and the limits of Verifpal analysis. &lt;em&gt;PrimitiveSpecs&lt;/em&gt; were introduced in order to standardize how primitives are defined and how their semantics operate within Verifpal analysis, i.e. via the following operations:&lt;br&gt;
• Decompose. Given a primitive’s output and a defined subset of its inputs, reveal one of its inputs. (Given ENC(k, m) and k, reveal m).&lt;br&gt;
• Recompose. Given a subset of a primitive’s outputs, reveal one of its inputs. (Given a, b, reveal x if a ,b,_ = SHAMIR_SPLIT(x)).&lt;br&gt;
• Rewrite. Given a matching defined pattern within a primitive’s inputs, rewrite the primitive expression itself into a logical subset of its inputs. (Given DEC(k, ENC(k, m)), rewrite the entire expression DEC(k, ENC(k, m)) to m).&lt;br&gt;
• Rebuild. Given a primitive whose inputs are all the outputs of some same other primitive, rewrite the primitive expression itself into a logical subset of its inputs. (Given SHAMIR_JOIN(a, b) where a, b, c = SHAMIR_SPLIT(x), rewrite the entire expression SHAMIR_JOIN(a, b) to x).&lt;/p&gt;
&lt;h2 id=&#34;understanding-verifpals-purpose-in-the-protocol-security-space&#34;&gt;Understanding Verifpal&amp;rsquo;s Purpose in the Protocol Security Space&lt;/h2&gt;
&lt;p&gt;Given Verifpal&amp;rsquo;s progress over its first year, it becomes ever more important to clearly answer the question: &lt;em&gt;&amp;ldquo;Where is Verifpal positioned within the protocol security space in comparison to other tools?&amp;rdquo;&lt;/em&gt; What is Verifpal able to do for me, the protocol analyst, engineer or applied cryptography practitioner?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Verifpal appears to be evolving towards being software best suited for designing, modeling, analyzing and testing cryptographic protocols.&lt;/strong&gt; Notably, this means that Verifpal is &lt;em&gt;not&lt;/em&gt; likely to progress in the direction where it functions as a producer of &lt;em&gt;protocol security proofs&lt;/em&gt;, nor does our experience lead us to believe that this is a space where more contributions are likely to be useful or impactful. This latter functionality will likely remain the jurisdiction of tools such as &lt;a href=&#34;https://tamarin-prover.github.io/&#34;&gt;Tamarin&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Verifpal&amp;rsquo;s goal is to focus on the real-world priority of exceeding these tools in terms of how much time and effort it will take for the engineer or practitioner to obtain a meaningful model of their protocol that gives them meaningful analysis answers.&lt;/strong&gt; However, the compromises to ease of use and modeling/analysis rapidity required for Verifpal to offer full proving capabilities are deemed too great given Verifpal&amp;rsquo;s design and functionality goals, and therefore will likely never be adopted fully into its functionality.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://symbolic.software/images/blog/image-1.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;For example, Verifpal&amp;rsquo;s strategy for dealing with state space explosion imposes limitations on the &lt;em&gt;completeness&lt;/em&gt; of its analysis, but this appears to only affect protocols that are unlikely to ever appear in real-world scenarios, while also granting Verifpal greater analysis speed and likelihood for analysis termination than other tools.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;In deciding Verifpal&amp;rsquo;s priorities, we slam the brakes at the moment where the learning curve, effort and analysis cost begin to have strongly diminishing returns for the user.&lt;/strong&gt; Our bet is that this path forward for Verifpal will lead to a hugely more substantial impact for engineers and practitioners than traditional automated proof modeling tools. In the example above, we can see how Verifpal makes compromises in analysis completeness that preclude its ability to output full proofs but that greatly increase the likelihood of analysis termination (a significant problem for tools such as &lt;a href=&#34;https://prosecco.gforge.inria.fr/personal/bblanche/proverif/&#34;&gt;ProVerif&lt;/a&gt;) without having an apparently significant impact on the analysis of real-world, non-Ivory-Tower protocols.&lt;/p&gt;
&lt;p&gt;Ergo, Verifpal&amp;rsquo;s main responsibility is to straddle a balance between soundness/reliability and ease of use/relevance to real-world practitioners. The way we approach this responsibility is by making sure that Verifpal&amp;rsquo;s semantics are formally specified, that its analysis methodology is amply documented, and that its code is easy to understand. &lt;a href=&#34;https://source.symbolic.software/verifpal/verifpal/-/blob/master/internal/libcoq/libcoqtemplate.v&#34;&gt;Verifpal&amp;rsquo;s formal semantics in Coq&lt;/a&gt;, the analysis methodology details in the &lt;a href=&#34;https://verifpal.com/paper&#34;&gt;Verifpal paper&lt;/a&gt; documented and easy-to-understand &lt;a href=&#34;https://source.symbolic.software/verifpal&#34;&gt;Go codebase&lt;/a&gt; aim to fulfill this purpose.&lt;/p&gt;
&lt;p&gt;Simultaneously, Verifpal utilizes this formally specified base in order to maintain the development of a language and framework that is idiomatic to the extreme. The Verifpal language is meant to illustrate protocols close to how one may describe them in an informal conversation, while still being precise and expressive enough for formal modeling. Verifpal avoids user error by not allowing users to define their own cryptographic primitives. Instead, it comes with built-in cryptographic functions which nevertheless are defined and which operate according to a formally specified standard with concrete semantics. All of this is coupled with a high standard for documentation, accessibility and support in popular workflows and code editors (via Verifpal for Visual Studio Code and VerifHub).&lt;/p&gt;
&lt;p&gt;So far, Verifpal&amp;rsquo;s chosen path has allowed it to provide value in the &lt;a href=&#34;https://blog.symbolic.software/2020/04/05/dp-3t-verifpal/&#34;&gt;quick modeling and correct analysis of the DP-3T pandemic contact tracing protocol as it was being specified&lt;/a&gt;, and has helped Zoom, Monocypher &lt;a href=&#34;https://verifpal.com/getinvolved/&#34;&gt;and others&lt;/a&gt; achieve quick protocol modeling and prototyping insight as they developed their protocols, by offering a methodology and framework that allowed results to be obtained in hours (sometimes minutes!) instead of weeks. We hope to continue making exciting developments in Verifpal well into 2021, and can&amp;rsquo;t wait to see how the community makes use of it.&lt;/p&gt;
</content:encoded>
      <category>Software</category>
      <category>Verifpal</category>
      <category>Formal Verification</category>
      
    </item>
    
    <item>
      <title>Better Queries for Verifpal</title>
      <link>https://symbolic.software/blog/2020-04-14-queries/</link>
      <pubDate>Tue, 14 Apr 2020 00:00:00 &#43;0000</pubDate>
      <guid>https://symbolic.software/blog/2020-04-14-queries/</guid>
      <description>Towards new queries, automated model translation and formalized semantics in Verifpal.</description>
      <content:encoded>&lt;p&gt;For the past few weeks, repeated requests have appeared for Verifpal to provide more analysis features in the way of &lt;strong&gt;detecting replay attacks&lt;/strong&gt; and also in supporting the analysis of the &lt;strong&gt;unlinkability&lt;/strong&gt; of values in protocols.&lt;/p&gt;
&lt;p&gt;Friedrich Wiemer pointed out that Verifpal was not flexible enough to &lt;a href=&#34;https://lists.symbolic.software/pipermail/verifpal/2020/000208.html&#34;&gt;detect replay attacks in Needham-Schroeder&lt;/a&gt;, while Anders N. also &lt;a href=&#34;https://lists.symbolic.software/pipermail/verifpal/2020/000218.html&#34;&gt;requested replay attack detection&lt;/a&gt; recently in relation to other protocols. Others have also made such a request. Unlinkability became a pertinent feature especially after our attempts to &lt;a href=&#34;https://blog.symbolic.software/2020/04/05/dp-3t-verifpal/&#34;&gt;sketch a model of the DP-3T pandemic-tracing protocol in Verifpal&lt;/a&gt;) last week.&lt;/p&gt;
&lt;p&gt;Furthermore, there have been many requests since Verifpal&amp;rsquo;s inception to allow for more in-depth analysis of Verifpal models using tools that have existed for decades longer, such as &lt;a href=&#34;https://proverif.inria.fr&#34;&gt;ProVerif&lt;/a&gt;, &lt;a href=&#34;https://cryptoverif.inria.fr/&#34;&gt;CryptoVerif&lt;/a&gt;, &lt;a href=&#34;https://tamarin-prover.github.io/&#34;&gt;Tamarin&lt;/a&gt; and &lt;a href=&#34;https://coq.inria.fr&#34;&gt;Coq&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As such, &lt;strong&gt;Verifpal 0.12.0&lt;/strong&gt; will introduce many new features that will allow it to expand and mature in ways meant to address the above:&lt;/p&gt;
&lt;h2 id=&#34;freshness-queries-in-verifpal&#34;&gt;Freshness Queries in Verifpal&lt;/h2&gt;
&lt;p&gt;Freshness queries are useful for detecting replay attacks. In passive attacker mode, a freshness query will check whether a value is &amp;ldquo;fresh&amp;rdquo; between sessions (i.e. if it has at least one composing element that is generated, non-static). In active attacker mode, it will check whether a value can be rendered &amp;ldquo;non-fresh” (i.e. static between sessions) and subsequently successfully used between sessions. An example of freshness queries is available in &lt;code&gt;examples/test/freshness.vp&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Freshness queries are currently fairly well-defined in themselves, but it is unclear still whether they are flexible enough to detect the kinds of replay attacks that are envisioned by our users. As such, further discussion is welcome on the &lt;a href=&#34;https://verifpal.com/list&#34;&gt;Verifpal Mailing List&lt;/a&gt; in order to understand how replay attack detection in Verifpal can be further improved.&lt;/p&gt;
&lt;h2 id=&#34;unlinkability-queries-in-verifpal&#34;&gt;Unlinkability Queries in Verifpal&lt;/h2&gt;
&lt;p&gt;Protocols such as DP-3T, voting protocols and RFID-based protocols posit an &lt;strong&gt;“unlinkability”&lt;/strong&gt; security property on some of their components or processes. Definitions for unlinkability vary wildly despite the best efforts of researchers. Complicating matters further, the actually-formalized definitions for unlinkability tend to pertain to &lt;em&gt;processes&lt;/em&gt; and not to &lt;em&gt;values&lt;/em&gt; like the definition used by, for example, DP-3T.&lt;/p&gt;
&lt;p&gt;In DP-3T, definitions for unlinkability are suggested to go along these lines: &lt;em&gt;&amp;ldquo;for two observed ephIDs, the adversary cannot distinguish between a game in which they belong to the same user and a game in which they belong to two different users.&lt;/em&gt;&amp;rdquo; (definition elucidated with &lt;a href=&#34;https://www.benjaminlipp.de/&#34;&gt;Benjamin Lipp&lt;/a&gt;; the DP-3T whitepaper itself &lt;a href=&#34;https://github.com/DP-3T/documents/issues/103&#34;&gt;does not currently have one&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Hirschi, Delaune et al have a couple of papers exploring the formalization of unlinkability: &lt;a href=&#34;https://arxiv.org/abs/1710.02049&#34;&gt;&lt;em&gt;A Method for Unbounded Verification of Privacy-type Properties&lt;/em&gt;&lt;/a&gt;, followed by &lt;em&gt;&lt;a href=&#34;https://hal.archives-ouvertes.fr/hal-02459984/document&#34;&gt;A Method for Proving Unlinkability of Stateful Protocols&lt;/a&gt;&lt;/em&gt;. In these papers, the unlinkability of processes is defined as the satisfaction of two properties: &lt;em&gt;“frame opacity”&lt;/em&gt; and &lt;em&gt;“well-authentication”:&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Frame opacity:&lt;/strong&gt; &lt;em&gt;&amp;ldquo;Intuitively, this condition aims to prevent attacks in which, for some possible behaviour of the attacker, there exists a relation between messages that leaks information about the involved agents. Practically speaking, this condition requires that any reachable frame must be statically equivalent to an idealised frame that does not depend on identity parameters. A very simple way to obtain an idealisation of a frame is to replace each output message by a fresh nonce. In that case, if the real frame and the idealised frame are statically equivalent, it is obvious that the attacker cannot learn anything by analysing the relations between the messages, since there is no relation between disctinct fresh nonces. Nevertheless, it is not satisfying because too restrictive as e.g. a pair is distinguishable from a nonce.”&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Well-authentication:&lt;/strong&gt; &lt;em&gt;&amp;ldquo;The idea behind this second condition is to avoid that the outcome of conditionals leaks information about identities to the attacker. To do so, we require that whenever a conditional (let or lookup) is positively evaluated, the corresponding agent is having an honest interaction with another participant. In practice, protocols often have some conditionals for which the attacker already knows the outcome: these safe conditionals can (and must) be excluded from our condition.”&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Frame opacity seems clear enough, and well-authentication seems to be the combination of the traditional notion of “strong authentication” or “mutual authentication”, combined with an assumption of honest protocol-following on behalf of the other party (i.e. the protocol &amp;ldquo;executing correctly&amp;rdquo;).&lt;/p&gt;
&lt;p&gt;Based on the above, Verifpal 0.12.0 will introduce experimental support for a notion of unlinkability in Verifpal that centers more on values than on processes. For example, one could write &lt;code&gt;unlinkability? a, b&lt;/code&gt; as a query to test whether &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt; are unlinkable from one another. Unlinkability checks for the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;First, Verifpal checks to see if &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt; satisfy freshness. If they do not, the query fails. Similarly to regular freshness queries, if an attacker can coerce a value to be non-fresh across sessions, then it is non-fresh and the query fails.&lt;/li&gt;
&lt;li&gt;If &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt; both satisfy freshness, Verifpal then checks to see if the attacker can determine them as being the output of the same primitive (for example, the first and second output of the same HKDF construction with the same inputs.) Of course, &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt; can indeed be the outputs of that HKDF and be unlinkable; unless the attacker is able to reconstruct that same HKDF primitive and thereby use it to determine that both values are the outputs of it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Further testing of this definition of unlinkability in Verifpal is &lt;strong&gt;strongly welcome&lt;/strong&gt;, as &lt;strong&gt;it is very much&lt;/strong&gt; &lt;strong&gt;expected&lt;/strong&gt; that it will be further elucidated since it is highly doubtful that it covers all cases on unlinkability. Again, &lt;a href=&#34;https://verifpal.com/list&#34;&gt;Verifpal Mailing List&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;formalizing-verifpal-in-coq-and-experimental-proverifcoq-model-generation&#34;&gt;Formalizing Verifpal in Coq, and Experimental ProVerif/Coq Model Generation&lt;/h2&gt;
&lt;p&gt;Work has been ongoing on allowing for the automated translation of Verifpal models to the languages of other tools for those interested in carrying out further analysis:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ProVerif:&lt;/strong&gt; &lt;a href=&#34;https://proverif.inria.fr&#34;&gt;ProVerif&lt;/a&gt; is the verification software that inspired Verifpal and which was written by my former thesis co-advisor Bruno Blanchet. ProVerif has existed for around 20 years and can perform a more diverse set of analyses than Verifpal despite both softwares belonging to the same category of verifiers (symbolic model protocol verifiers).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Coq&lt;/strong&gt;: &lt;a href=&#34;https://coq.inria.fr&#34;&gt;Coq&lt;/a&gt; is a full-fat theorem prover that can do way more than handle the description of protocols. &lt;a href=&#34;https://georgio.xyz&#34;&gt;Georgio Nicolas&lt;/a&gt; has been handling Coq translations, with &lt;a href=&#34;https://scholar.google.com.au/citations?user=o8zCZV4AAAAJ&amp;amp;hl=en&#34;&gt;Mukesh Tiwari&lt;/a&gt; recently joining our effort and lending a hand (welcome, Mukesh!). &lt;strong&gt;Currently, we have fully formalized the semantics of Verifpal in Coq&lt;/strong&gt;, and we are working on also formalizing the attacker and verification logic in Coq as well for both active and passive attackers. &lt;strong&gt;At that point, we will have fully implemented Verifpal in Coq.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Experimental ProVerif and Coq model generation will be available for testing in Verifpal 0.12.0. Type &lt;code&gt;verifpal help&lt;/code&gt; to find out more on how to use Verifpal to generate these models.&lt;/p&gt;
&lt;p&gt;Once support for ProVerif and Coq in Verifpal has matured, and especially once attacker and verification logic is captured in Coq on top of the current semantics, another more detailed blog post will follow.&lt;/p&gt;
&lt;h2 id=&#34;looking-towards-a-bright-2020&#34;&gt;Looking Towards a Bright 2020&lt;/h2&gt;
&lt;p&gt;Finally, Verifpal 0.12.0 will also support phases in passive attacker mode, and not just in active attacker mode as before.&lt;/p&gt;
&lt;p&gt;These developments in Verifpal are incredibly exciting. They come hot on the heels of many other new features and improvements that have been introduced so far in 2020:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;New primitives&lt;/strong&gt;, such as &lt;a href=&#34;https://lists.symbolic.software/pipermail/verifpal/2020/000126.html&#34;&gt;public-key encryption&lt;/a&gt;, &lt;a href=&#34;https://lists.symbolic.software/pipermail/verifpal/2020/000127.html&#34;&gt;Shamir secret sharing&lt;/a&gt; and &lt;a href=&#34;https://lists.symbolic.software/pipermail/verifpal/2020/000161.html&#34;&gt;ring signatures&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multi-threaded,&lt;/strong&gt; &lt;a href=&#34;https://lists.symbolic.software/pipermail/verifpal/2020/000128.html&#34;&gt;concurrent analysis&lt;/a&gt; using Go&amp;rsquo;s excellent multithreading support. Put that gaming PC to work!&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Password-hashing&lt;/strong&gt; modeling, which allows for &lt;a href=&#34;https://lists.symbolic.software/pipermail/verifpal/2020/000132.html&#34;&gt;models to capture&lt;/a&gt; when a weak/guessable/bruteforceable password is used as, for example, an encryption or signing key.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;leaks&lt;/code&gt; expression&lt;/strong&gt;, which allows simulating the leaking of a value on the network without sending is as a message.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&#34;https://source.symbolic.software/verifpal/verifpal/-/commit/780dc774226390eb8183c7ccfa61ed83a9d26e56&#34;&gt;Queries as preconditions&lt;/a&gt;&lt;/strong&gt; for messages.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of this is in line with our announced &lt;a href=&#34;https://lists.symbolic.software/pipermail/verifpal/2020/000129.html&#34;&gt;Verifpal 2020 plans&lt;/a&gt;, and the progress on these plans so far has been highly encouraging.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&#34;https://lists.symbolic.software/pipermail/verifpal/2020/000134.html&#34;&gt;list of projects using Verifpal&lt;/a&gt; continues to grow, and none of this would have been possible without the help of our supporters:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;NLNet Foundation:&lt;/strong&gt; Verifpal is sponsored by the &lt;a href=&#34;https://nlnet.nl/&#34;&gt;NLNet Foundation&lt;/a&gt;. Funding was provided through the &lt;a href=&#34;https://www.ngi.eu/about/ngi-zero/&#34;&gt;&lt;em&gt;NGI0 Privacy Enhancing Technologies Fund&lt;/em&gt;&lt;/a&gt;, a fund established by NLnet with financial support from the European Commission’s &lt;em&gt;Next Generation Internet&lt;/em&gt; program, under the aegis of DG Communications Networks, Content and Technology under grant agreement №825310.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cure53:&lt;/strong&gt; Verifpal is also supported by &lt;a href=&#34;https://cure53.de/&#34;&gt;Cure53&lt;/a&gt;, a Berlin-based security auditing firm which provides penetration testing for online services, security analysis and architectural advice for security and cryptographic applications, training, consulting, incident management and malware analysis.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And, of course, users like you. Thank you!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Get involved&lt;/strong&gt; in Verifpal discussions today, either through:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&#34;https://verifpal.com/list&#34;&gt;Verifpal Mailing List&lt;/a&gt;&lt;/strong&gt;, for more long-form and serious discussions, or,&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&#34;https://verifpal.com/discord&#34;&gt;Verifpal Discord&lt;/a&gt;&lt;/strong&gt;, where you can chat with Verifpal contributors, users and enthusiasts!&lt;/li&gt;
&lt;/ul&gt;
</content:encoded>
      <category>Software</category>
      <category>Research</category>
      <category>Verifpal</category>
      <category>Formal Verification</category>
      
    </item>
    
    <item>
      <title>Modeling DP-3T With Verifpal</title>
      <link>https://symbolic.software/blog/2020-04-05-dp3t/</link>
      <pubDate>Sun, 05 Apr 2020 00:00:00 &#43;0000</pubDate>
      <guid>https://symbolic.software/blog/2020-04-05-dp3t/</guid>
      <description>How Verifpal sped up the formal modeling efforts for a new pandemic-tracing Protocol.</description>
      <content:encoded>&lt;p&gt;Last week, numerous researchers published the timely fruits of their recent collaboration to provide a proximity-tracking solution that can help during pandemics while still being &lt;em&gt;privacy-preserving&lt;/em&gt;: the result, &lt;a href=&#34;https://github.com/DP-3T/documents&#34;&gt;&lt;strong&gt;Decentralized Privacy-Preserving Proximity Tracing&lt;/strong&gt; (DP-3T)&lt;/a&gt;, provides a promising first step in bringing real-world cryptography into the effort to combat the COVID-19 pandemic. As mentioned in Troncoso et al.&amp;rsquo;s whitepaper, the goal of DP-3T is:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&amp;quot;&amp;hellip;to simplify and accelerate the process of identifying people who have been in contact with an infected person, thus providing a technological foundation to help slow the spread of the SARS-CoV-2 virus. The system aims to minimise privacy and security risks for individuals and communities and guarantee the highest level of data protection.&amp;quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Troncoso et al, Decentralized Privacy-Preserving Proximity Tracing&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;In support of this project, we at Symbolic Software decided to test out &lt;a href=&#34;https://verifpal.com&#34;&gt;Verifpal&lt;/a&gt;, our open source protocol verification framework, to see how well its promises of easier and more accessible protocol modeling and verification can hold up in the face of a new and ambitious protocol that targets a novel use case.&lt;/p&gt;
&lt;p&gt;In this post, we will go through DP-3T while modeling it using the Verifpal Language, and conclude by comparing our results to the designers&amp;rsquo; security goals.&lt;/p&gt;
&lt;h3 id=&#34;modeling-dp-3t&#34;&gt;Modeling DP-3T&lt;/h3&gt;
&lt;p&gt;To demonstrate DP-3T, we will assume that the principals participating in this simulation are the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A population of 3 individuals: Alice, Bob, and Charlie, each of them possessing a smartphone: &lt;code&gt;SmartphoneA&lt;/code&gt;, &lt;code&gt;SmartphoneB&lt;/code&gt;, and &lt;code&gt;SmartphoneC&lt;/code&gt; respectively;&lt;/li&gt;
&lt;li&gt;A Healthcare Authority serving this population;&lt;/li&gt;
&lt;li&gt;A Backend Server, that individuals can communicate with to obtain daily information.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After installing &lt;a href=&#34;https://verifpal.com&#34;&gt;Verifpal&lt;/a&gt;, we can start by creating a new model called &amp;ldquo;dp-3t.vp&amp;rdquo; in which we begin by defining an attacker which matches with our security model. In this case we will be using an active attacker (i.e. one that can not only monitor but also intercept and overwrite unprotected messages on the network):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;attacker[active]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We then proceed to illustrate our model as a sequence of days in which DP-3T is in operation within the lifecycle of a pandemic.&lt;/p&gt;
&lt;h3 id=&#34;day-0-setup-phase&#34;&gt;Day 0 (setup phase)&lt;/h3&gt;
&lt;p&gt;We assume that no new individuals were diagnosed with the disease on Day 0 of using DP-3T. This means that the Healthcare Authority and the Backend Server will not act at this stage and we can simply ignore them for now.&lt;/p&gt;
&lt;p&gt;The DP-3T specification states that every principal, when first joining the system, should gene_rate a random secret key_ (&lt;code&gt;SK&lt;/code&gt;) to be used for one day only. For every &lt;code&gt;SK&lt;/code&gt; value, and the knowledge of a public &amp;ldquo;&lt;em&gt;broadcast key&lt;/em&gt;&amp;rdquo; value, principals should compute multiple &lt;em&gt;Unique Ephemeral ID&lt;/em&gt; values (&lt;code&gt;EphID&lt;/code&gt;) using a combination of a PRG and a PRF. The method of generating &lt;code&gt;EphID&lt;/code&gt; is analogous with the &lt;code&gt;HKDF&lt;/code&gt; function from Verifpal. We could add the following lines of code to our file in order to model Alice&amp;rsquo;s &lt;code&gt;SmartphoneA&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-gdscript3&#34; data-lang=&#34;gdscript3&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;//&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;All&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;lines&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;that&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;start&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;with&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;//&amp;#34;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;are&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;treated&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;as&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;comments&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;and&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ignored&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;by&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Verifpal&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;//&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;A&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;principal&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;block&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;looks&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;like&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;the&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;following&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;principal&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;SmartphoneA&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;//&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;In&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;the&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;line&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;below&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;we&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;state&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;that&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Alice&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;knows&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;the&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;BroadcastKey&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;knows&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;BroadcastKey&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;//&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;SK&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;is&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;going&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;to&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;be&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;a&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;secret&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;random&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;value&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;//&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;To&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;define&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;it&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;we&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;the&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;generates&amp;#34;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;keyword&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;//&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;We&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;will&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;the&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;following&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;template&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;SK&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;variable&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;names&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;//&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;SK&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;day&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;number&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;][&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;principal&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;initial&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;generates&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;SK0A&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;//&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;We&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;will&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;the&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;following&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;template&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;EphID&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;variable&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;names&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;//&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;EphID&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;day&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;number&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;][&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;value&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;number&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;][&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;principal&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;initial&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;EphID00A&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;EphID01A&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;EphID02A&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;HKDF&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;nil&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;SK0A&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;BroadcastKey&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The same thing goes for Bob, and Charlie:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;principal SmartphoneB[
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    knows public BroadcastKey
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    generates SK0B
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    EphID00B, EphID01B, EphID02B = HKDF(nil, SK0B, BroadcastKey)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;principal SmartphoneC[
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    knows public BroadcastKey
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    generates SK0C
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    EphID00C, EphID01C, EphID02C = HKDF(nil, SK0C, BroadcastKey)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Whenever two principals would come be in physical proximity of each other, they would automatically exchange &lt;code&gt;EphIDs&lt;/code&gt;. Once a principal uses an &lt;code&gt;EphID&lt;/code&gt; value, they discard it and use another one when performing an exchange with another principal.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s imagine that Alice and Bob came into contact. It would mean that Alice sent &lt;code&gt;EphID00A&lt;/code&gt; in a message to Bob and that Bob sent &lt;code&gt;EphID00B&lt;/code&gt; to Alice:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://symbolic.software/images/blog/axb.webp&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;Here is how the above message exchange is modeled in Verifpal:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;// Sender -&amp;gt; Recipient : Name of Value
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;SmartphoneA -&amp;gt; SmartphoneB: EphID00A
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;SmartphoneB -&amp;gt; SmartphoneA: EphID00B
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now, let&amp;rsquo;s say that in the conclusion of Day 0, Bob sits behind Charlie in the Bus:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://symbolic.software/images/blog/bxc.webp&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;Modeling this is equally simple:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;SmartphoneC -&amp;gt; SmartphoneB: EphID01C
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;SmartphoneB -&amp;gt; SmartphoneC: EphID01B
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;day-1&#34;&gt;Day 1&lt;/h3&gt;
&lt;p&gt;On Day 1, the Backend Server will automatically publish the &lt;code&gt;SK&lt;/code&gt; values of people who were infected to the members of the general population. These values were previously unpublished and thus were private and only known by their generators and the server.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://symbolic.software/images/blog/d1s.webp&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;// A server is just like any other principal
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;principal BackendServer[
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    // Let&amp;#39;s assume that infectedPatients0 is the list of infected patients on day 0
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    knows private infectedPatients0
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;BackendServer -&amp;gt; SmartphoneA: infectedPatients0
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;BackendServer -&amp;gt; SmartphoneB: infectedPatients0
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;BackendServer -&amp;gt; SmartphoneC: infectedPatients0
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We should not forget that every day starting from Day 1, DP-3T mandates that principals will generate new &lt;code&gt;SK&lt;/code&gt; values. The new value will be equal to the hash of the &lt;code&gt;SK&lt;/code&gt; value from the day before. Principals will also generate &lt;code&gt;EphIDs&lt;/code&gt; just like before.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;principal SmartphoneA[
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    SK1A = HASH(SK0A)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    EphID10A, EphID11A, EphID12A = HKDF(nil, SK1A, BroadcastKey)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;principal SmartphoneB[
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    SK1B = HASH(SK0B)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    EphID10B, EphID11B, EphID12B = HKDF(nil, SK1B, BroadcastKey)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;principal SmartphoneC[
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    SK1C = HASH(SK0C)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    EphID10C, EphID11C, EphID12C = HKDF(nil, SK1C, BroadcastKey)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Thankfully, Alice, Bob and Charlie are committed to self-confinement and have stayed at home, so they did not exchange &lt;code&gt;EphIDs&lt;/code&gt; with anyone.&lt;/p&gt;
&lt;h3 id=&#34;day-2&#34;&gt;Day 2&lt;/h3&gt;
&lt;p&gt;On Day 2, a similar sequence of events takes place. Since it is sufficient to define the values that we will need later on in our model, we will just define a block for Alice.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;principal SmartphoneA[
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    SK2A = HASH(SK1A)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    EphID20A, EphID21A, EphID22A = HKDF(nil, SK2A, BroadcastKey)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;fast-forward-to-day-15&#34;&gt;Fast-Forward to Day 15&lt;/h3&gt;
&lt;p&gt;Unfortunately, Alice tests positive for COVID-19. Since this breaks the routine that happened between Day 1 and Day 15, we will &lt;a href=&#34;https://lists.symbolic.software/pipermail/verifpal/2020/000135.html&#34;&gt;announce a new phase&lt;/a&gt; in our protocol model:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;phase[1]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Alice decides to announce her infection anonymously using DP-3T. This means that she will have to securely communicate &lt;code&gt;SK1A&lt;/code&gt; (her &lt;code&gt;SK&lt;/code&gt; value from 14 days ago) to the Backend Server, using a unique trigger token provided by the healthcare authority. Assuming that the Backend Server and the Healthcare Authority share a secure connection, and that a private key encryption key &lt;code&gt;ephemeral_sk&lt;/code&gt; has been exchanged off the wire by the Healthcare Authority, Alice, and the Backend Server, the Healthcare Authority will encrypt a freshly generated &lt;code&gt;triggerToken&lt;/code&gt; using &lt;code&gt;ephemeral_sk&lt;/code&gt; and send it to both Alice and the Backend Server.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;principal HealthCareAuthority[
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    generates triggerToken
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    knows private ephemeral_sk
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    m1 = ENC(ephemeral_sk, triggerToken)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;// The brackets around m1 here mean that the value is guarded
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;// ie: an active attacker cannot inject a value in its place
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;HealthCareAuthority -&amp;gt; BackendServer : [m1]
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;HealthCareAuthority -&amp;gt; SmartphoneA : m1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then, Alice would have to use an AEAD cipher to encrypt &lt;code&gt;SK1A&lt;/code&gt; using &lt;code&gt;ephemeral_sk&lt;/code&gt; as the key and &lt;code&gt;triggerToken&lt;/code&gt; as additional data and send the output to the Backend Server. Note that Alice can only obtain &lt;code&gt;triggerToken&lt;/code&gt; after decrypting &lt;code&gt;m1&lt;/code&gt; using &lt;code&gt;ephemeral_sk&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;principal SmartphoneA[
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    knows private ephemeral_sk
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    m1_dec = DEC(ephemeral_sk, m1)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    m2 = AEAD_ENC(ephemeral_sk, SK1A, m1_dec)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;SmartphoneA -&amp;gt; BackendServer: m2
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The Backend Server will now have to decrypt &lt;code&gt;m1&lt;/code&gt; to receive the &lt;code&gt;triggerToken&lt;/code&gt; in the same way that Alice did, then attempt to decrypt &lt;code&gt;m2&lt;/code&gt;. If that decryption was successful, the server would obtain &lt;code&gt;SK1A&lt;/code&gt; and would be sure that the value came from Alice because it is only Alice who knows both &lt;code&gt;triggerToken&lt;/code&gt; and &lt;code&gt;SK1A&lt;/code&gt; at the same time as defined in the protocol.&lt;/p&gt;
&lt;p&gt;Finally, the Backend Server will add &lt;code&gt;SK1A&lt;/code&gt; to the list of infected patients previously defined, and then send this list to all of the individuals in this community.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;principal BackendServer [
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    knows private ephemeral_sk
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    m2_dec = AEAD_DEC(ephemeral_sk, m2, DEC(ephemeral_sk, m1))?
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    infectedPatients1 = CONCAT(infectedPatients0, m2_dec)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;BackendServer -&amp;gt; SmartphoneA: infectedPatients1
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;BackendServer -&amp;gt; SmartphoneB: infectedPatients1
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;BackendServer -&amp;gt; SmartphoneC: infectedPatients1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Everything that happened in Day 15 can be summarized in the following diagram:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://symbolic.software/images/blog/sab.webp&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;queries&#34;&gt;Queries&lt;/h3&gt;
&lt;p&gt;Now, we may finally define the queries block, in which we ask Verifpal about the state of certain security guarantees that we expect from the protocol.&lt;/p&gt;
&lt;p&gt;Since&lt;code&gt;SK1A&lt;/code&gt; is now shared publicly, the DP-3T software running on anyone&amp;rsquo;s phone should be able to re-generate all &lt;code&gt;EphID&lt;/code&gt; values generated by the owner of &lt;code&gt;SK1A&lt;/code&gt; starting from 14 days prior to the day of diagnosis. These values would then be compared them with the list of &lt;code&gt;EphIDs&lt;/code&gt; they have received. Everyone who came in contact with Alice will therefore be notified that they have exchanged &lt;code&gt;EphIDs&lt;/code&gt; with someone who has been diagnosed with the illness without revealing the identity of that person.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;queries[
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    // Would someone who shared a value 15 days before they got tested get flagged?
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    // ie in phase[0], before phase[1]
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    confidentiality? EphID02A
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    // Will people who came in contact with Alice be able to compute
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        // all of Alice&amp;#39;s EphIDs starting from Day 1
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    confidentiality? EphID10A
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    confidentiality? EphID11A
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    confidentiality? EphID12A
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    confidentiality? EphID20A
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    confidentiality? EphID21A
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    confidentiality? EphID22A
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    // Is the server able to Authenticate Alice as the sender of m2
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    authentication? SmartphoneA -&amp;gt; BackendServer: m2
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The results of our initial modeling in Verifpal suggest to us the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No &lt;code&gt;EphIDs&lt;/code&gt; generated by Alice are known by any parties before Alice announces her illness.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;EphID02A&lt;/code&gt; remains confidential even after Alice declaring her illness. Note that it was generated 15 Days before Alice got tested.&lt;/li&gt;
&lt;li&gt;All of the following values &lt;code&gt;EphID10A&lt;/code&gt;, &lt;code&gt;EphID11A&lt;/code&gt;, &lt;code&gt;EphID12A&lt;/code&gt;, &lt;code&gt;EphID20A&lt;/code&gt;, &lt;code&gt;EphID21A&lt;/code&gt;, &lt;code&gt;EphID22A&lt;/code&gt; have been recoverable by an attacker in &lt;code&gt;phase[1]&lt;/code&gt; after Alice announces her illness.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These results come in line with what is expected from the protocol. We note that the security of communication channels between Healthcare Authorities, Backend Servers, and Individuals have not been defined, and we have placed our hypothetical own security conditions with in order to focus on quickly sketching the DP-3T protocol. Further analysis will be required in order to better elucidate the extent of the obtained security guarantees.&lt;/p&gt;
&lt;h3 id=&#34;generating-models-for-further-analysis-in-proverif-and-coq&#34;&gt;Generating Models for Further Analysis in ProVerif and Coq&lt;/h3&gt;
&lt;p&gt;&lt;img src=&#34;https://symbolic.software/images/blog/euwfggmxyaetit9-1.jpeg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;Verifpal generating ProVerif models live from the Verifpal model, through the Verifpal Visual Studio Code extension.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;re very excited to be working on automatically generating &lt;a href=&#34;http://proverif.inria.fr&#34;&gt;ProVerif&lt;/a&gt; and &lt;a href=&#34;http://coq.inria.fr&#34;&gt;Coq&lt;/a&gt; models directly from Verifpal models. The resulting ProVerif and Coq models will be human-readable and thus easily extensible, allowing for more profound protocol analysis and supplementing Verifpal&amp;rsquo;s insight with that of tools that have existed for more than two decades.&lt;/p&gt;
&lt;p&gt;In working on DP-3T, we were able to generate baseline ProVerif and Coq models after less than an hour of work on the Verifpal model itself. This is an immeasurably huge leap forward in terms of obtaining working material for the formal modeling and analysis of a novel protocol, and we are &lt;em&gt;very&lt;/em&gt; excited to post further updates as this new feature matures in Verifpal.&lt;/p&gt;
&lt;p&gt;ProVerif and Coq model generation is currently under development, and we expect a beta release to be ready by the end of April.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://symbolic.software/images/blog/verif-coq-test.webp&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;Coming soon!&lt;/p&gt;
</content:encoded>
      <category>Software</category>
      <category>Research</category>
      <category>Verifpal</category>
      <category>Formal Verification</category>
      
    </item>
    
    <item>
      <title>Booking confirmed</title>
      <link>https://symbolic.software/chat/confirmation/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 &#43;0000</pubDate>
      <guid>https://symbolic.software/chat/confirmation/</guid>
      <description>Thanks for booking a call with Symbolic Software.</description>
      <content:encoded>
&lt;section class=&#34;confirmation&#34;&gt;
	&lt;div class=&#34;container&#34;&gt;
		&lt;h1&gt;Speak soon.&lt;/h1&gt;
		&lt;p&gt;Thanks for booking a call. We&#39;re looking forward to learning more about what you&#39;re building. If there&#39;s any context you&#39;d like to send ahead of the call, drop us an email at &lt;a href=&#34;mailto:business@symbolic.software&#34;&gt;business@symbolic.software&lt;/a&gt;.&lt;/p&gt;

		&lt;div class=&#34;confirmation-steps&#34;&gt;
			&lt;div class=&#34;confirmation-step&#34;&gt;
				&lt;span class=&#34;num&#34;&gt;01&lt;/span&gt;
				&lt;span class=&#34;text&#34;&gt;You&#39;ll receive a calendar invite within a few minutes. Check your spam folder if you don&#39;t see it.&lt;/span&gt;
			&lt;/div&gt;
			&lt;div class=&#34;confirmation-step&#34;&gt;
				&lt;span class=&#34;num&#34;&gt;02&lt;/span&gt;
				&lt;span class=&#34;text&#34;&gt;On the call, we&#39;ll scope the work together — what you&#39;re building, what threat models matter, what your team is comfortable with.&lt;/span&gt;
			&lt;/div&gt;
			&lt;div class=&#34;confirmation-step&#34;&gt;
				&lt;span class=&#34;num&#34;&gt;03&lt;/span&gt;
				&lt;span class=&#34;text&#34;&gt;If we agree on a fit, we send a written scope and timeline within a week.&lt;/span&gt;
			&lt;/div&gt;
		&lt;/div&gt;

		&lt;a class=&#34;confirmation-back&#34; href=&#34;https://symbolic.software/&#34;&gt;← Back to home&lt;/a&gt;
	&lt;/div&gt;
&lt;/section&gt;
</content:encoded>
      
    </item>
    
  </channel>
</rss>
