<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
    <channel>
      <title>Crypterial Labs</title>
      <link>https://crypterial.com</link>
      <description>Crypterial Labs is a research and engineering lab for cryptography: post-quantum standards, proof systems, privacy-preserving computation, and AI.</description>
      <generator>Zola</generator>
      <language>en</language>
      <atom:link href="https://crypterial.com/rss.xml" rel="self" type="application/rss+xml"/>
      <lastBuildDate>Fri, 02 Oct 2026 00:00:00 +0000</lastBuildDate>
      <item>
          <title>crypto-pq 0.1.0: ML-KEM in no_std Rust</title>
          <pubDate>Thu, 17 Sep 2026 00:00:00 +0000</pubDate>
          <author>Wilson Tran</author>
          <link>https://crypterial.com/blog/crypto-pq-0-1-0/</link>
          <guid>https://crypterial.com/blog/crypto-pq-0-1-0/</guid>
          <description xml:base="https://crypterial.com/blog/crypto-pq-0-1-0/">&lt;p&gt;&lt;strong&gt;Update, 2 October 2026.&lt;/strong&gt; This release was an experimental prototype and is withdrawn. It
was never reviewed by a cryptographer, and it relied on third-party crates for SHA-3,
constant-time comparison and memory wiping, which the lab no longer allows. The crates on
crates.io now carry a withdrawal notice. crypto-pq is being redesigned from scratch, with no
external dependencies and only standardized algorithms. The rest of this post describes the
prototype as it was released.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;crypto-pq&lt;/code&gt; is the library the lab was formed around: post-quantum cryptography with one
contract across languages, starting with a Rust reference implementation of the NIST
standards. Version 0.1.0 is on &lt;a rel=&quot;external&quot; href=&quot;https://crates.io/crates/crypto-pq&quot;&gt;crates.io&lt;/a&gt; and
&lt;a rel=&quot;external&quot; href=&quot;https://github.com/crypterial/crypto-pq&quot;&gt;GitHub&lt;/a&gt;. It ships one algorithm, ML-KEM, and this
post is about what that took and what “ships” means here.&lt;/p&gt;
&lt;h2 id=&quot;what-is-in-the-box&quot;&gt;What is in the box&lt;/h2&gt;
&lt;p&gt;ML-KEM is the key-encapsulation mechanism standardized in &lt;a rel=&quot;external&quot; href=&quot;https://csrc.nist.gov/pubs/fips/203/final&quot;&gt;FIPS 203&lt;/a&gt;
in August 2024, the first of the post-quantum standards to reach final status. All three
parameter sets are implemented:&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Parameter set&lt;/th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Encapsulation key&lt;/th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Decapsulation key&lt;/th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Ciphertext&lt;/th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Shared secret&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;ML-KEM-512&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;800 bytes&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1 632 bytes&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;768 bytes&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;32 bytes&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;ML-KEM-768&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1 184 bytes&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;2 400 bytes&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1 088 bytes&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;32 bytes&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;ML-KEM-1024&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1 568 bytes&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;3 168 bytes&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1 568 bytes&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;32 bytes&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The paths follow one rule, &lt;code&gt;crypto_pq::&amp;lt;category&amp;gt;::&amp;lt;algorithm&amp;gt;_&amp;lt;parameter set&amp;gt;&lt;/code&gt;, so the
code reads the same way the standard does:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-17 z-d-17&quot;&gt;use&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt; crypto_pq&lt;/span&gt;&lt;span class=&quot;z-l-16 z-d-16&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt;kem&lt;/span&gt;&lt;span class=&quot;z-l-16 z-d-16&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt;ml_kem_768&lt;/span&gt;&lt;span class=&quot;z-l-16 z-d-16&quot;&gt;::&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;generate_keypair&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt; Ciphertext&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt; EncapsulationKey&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-10 z-l-i z-d-4 z-d-i&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-10 z-l-i z-d-4 z-d-i&quot;&gt; Key generation includes the pairwise-consistency check of FIPS 203 §7.1.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-17 z-d-17&quot;&gt;let&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;dk&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; ek&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-16 z-d-16&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-14 z-d-18&quot;&gt; generate_keypair&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-16 z-d-16&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-17 z-d-17&quot;&gt;mut&lt;/span&gt;&lt;span&gt; rng&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-16 z-d-16&quot;&gt;?&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-10 z-l-i z-d-4 z-d-i&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-10 z-l-i z-d-4 z-d-i&quot;&gt; The other side receives the encapsulation key as bytes and validates it.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-17 z-d-17&quot;&gt;let&lt;/span&gt;&lt;span&gt; ek&lt;/span&gt;&lt;span class=&quot;z-l-16 z-d-16&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt; EncapsulationKey&lt;/span&gt;&lt;span class=&quot;z-l-16 z-d-16&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-14 z-d-18&quot;&gt;from_bytes&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ek&lt;/span&gt;&lt;span class=&quot;z-l-16 z-d-16&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-14 z-d-18&quot;&gt;as_bytes&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-16 z-d-16&quot;&gt;?&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-17 z-d-17&quot;&gt;let&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;shared_secret_sender&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; ct&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-16 z-d-16&quot;&gt; =&lt;/span&gt;&lt;span&gt; ek&lt;/span&gt;&lt;span class=&quot;z-l-16 z-d-16&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-14 z-d-18&quot;&gt;encapsulate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-16 z-d-16&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-17 z-d-17&quot;&gt;mut&lt;/span&gt;&lt;span&gt; rng&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-17 z-d-17&quot;&gt;let&lt;/span&gt;&lt;span&gt; shared_secret_receiver&lt;/span&gt;&lt;span class=&quot;z-l-16 z-d-16&quot;&gt; =&lt;/span&gt;&lt;span&gt; dk&lt;/span&gt;&lt;span class=&quot;z-l-16 z-d-16&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-14 z-d-18&quot;&gt;decapsulate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-16 z-d-16&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;ct&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-14 z-d-18&quot;&gt;assert_eq!&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;shared_secret_sender&lt;/span&gt;&lt;span class=&quot;z-l-16 z-d-16&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-14 z-d-18&quot;&gt;as_bytes&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; shared_secret_receiver&lt;/span&gt;&lt;span class=&quot;z-l-16 z-d-16&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-14 z-d-18&quot;&gt;as_bytes&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Keys can also be derived deterministically from the 64-byte seed the standard defines
(&lt;code&gt;d‖z&lt;/code&gt;), which is what you want for storage and for test vectors.&lt;/p&gt;
&lt;h2 id=&quot;the-rules&quot;&gt;The rules&lt;/h2&gt;
&lt;p&gt;Everything in the workspace follows the same five rules, and they are the reason the first
release is one algorithm rather than five.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Only post-quantum public-key algorithms live here. The symmetric primitives they need
internally (SHA-3, SHAKE) come from audited external crates.&lt;/li&gt;
&lt;li&gt;An algorithm is labeled &lt;em&gt;production&lt;/em&gt; only when its standard is final and its crate passes
every gate in CI. Until a third-party audit exists, the label stays &lt;em&gt;not yet audited&lt;/em&gt;, and
that is the label on this release.&lt;/li&gt;
&lt;li&gt;Draft standards stay behind &lt;code&gt;unstable-*&lt;/code&gt; features that pin the draft revision.&lt;/li&gt;
&lt;li&gt;Standard parameter sets only. No custom variants, no reduced parameters.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;no_std&lt;/code&gt;, no allocator, &lt;code&gt;#![forbid(unsafe_code)]&lt;/code&gt;, secret-independent control flow and
memory access, zeroization of secret material.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;what-passes-every-gate-means-for-ml-kem&quot;&gt;What “passes every gate” means for ML-KEM&lt;/h2&gt;
&lt;p&gt;The tests are the part of the release we care most about, so here is the list.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;NIST ACVP vectors&lt;/strong&gt; for key generation, encapsulation, and decapsulation, all three
parameter sets, pinned to a commit of the official repository and checked by SHA-256 in CI so
they cannot drift silently.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Negative vectors from C2SP’s CCTV project&lt;/strong&gt;: encapsulation keys with coefficients outside
the modulus, and ciphertexts that differ from a valid one only in ways that must be caught by
the implicit-rejection path. Both are the cases implementations historically get wrong.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ten thousand random ML-KEM-768 round trips cross-checked against Go’s &lt;code&gt;crypto/mlkem&lt;/code&gt;&lt;/strong&gt;,
compared as accumulated hashes, so two independent implementations agree byte for byte on
keys, ciphertexts, and secrets.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Exhaustive tests of every modular reduction&lt;/strong&gt;: the Barrett and division-by-q helpers are
checked for every input in their domain (2²⁶ values for the Barrett step), not sampled. A
reduction that is wrong for one input in sixty-seven million is the kind of bug that survives
random testing for years.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A &lt;code&gt;no_std&lt;/code&gt; build for &lt;code&gt;thumbv7em-none-eabihf&lt;/code&gt;&lt;/strong&gt; and a build on the minimum supported Rust
version (1.85), both in CI.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One thing the vectors taught us: a few of the widely circulated ML-KEM test files still
encode the draft standard (they omit the domain-separation byte that FIPS 203 added to
key generation), and an implementation that “passes” them is passing the wrong thing. We will
write that up separately.&lt;/p&gt;
&lt;h2 id=&quot;what-is-next&quot;&gt;What is next&lt;/h2&gt;
&lt;p&gt;ML-DSA (FIPS 204) is next in the Rust workspace, followed by the first implementation in a
second language against the same contract. The categories the paths reserve, &lt;code&gt;sig&lt;/code&gt;,
&lt;code&gt;sig_stateful&lt;/code&gt;, &lt;code&gt;aead&lt;/code&gt;, &lt;code&gt;hash&lt;/code&gt;, &lt;code&gt;xof&lt;/code&gt;, &lt;code&gt;mac&lt;/code&gt;, &lt;code&gt;kdf&lt;/code&gt;, &lt;code&gt;rng&lt;/code&gt;, are the map; only &lt;code&gt;kem&lt;/code&gt; is
territory so far.&lt;/p&gt;
&lt;p&gt;If you use the crate, the fastest way to help is to read it. The design decisions are recorded
with the code so they can be checked rather than taken on trust, and the
&lt;a rel=&quot;external&quot; href=&quot;https://github.com/crypterial/crypto-pq/security/policy&quot;&gt;security policy&lt;/a&gt; says how to report
what you find.&lt;/p&gt;
</description>
      </item>
    </channel>
</rss>
