Changelog

All notable changes to this project are documented in this file.

The format is based on Keep a Changelog 1.1.0, and the workspace follows Semantic Versioning 2.0.0. The pre-1.0 line ships breaking changes in minor version bumps (0.1.x0.2.0); patch versions remain backwards-compatible.

Unreleased

  • Continued T1 / T2 SCA hardening per the Roadmap chapters of quantica/README.md and arcana/README.md.

  • Veille-trigger formalisation — record the veille window in CHANGELOG.md at every release bump (<window dates>; items reviewed: …; items folded into release: ) so the discipline becomes auditable.

  • Shake out the ported GitHub Actions workflows on the first runs (runner-image deltas: package availability, preinstalled toolchain versions, Pages deployment permissions).

  • bench bin-name collision between quantica_bench and arcana_bench — verify the cargo warning is gone at the next bump and rename one [[bin]] if not.

[0.2.0] - 2026-07-29

The second pre-1.0 cut. Highlights: the shared krypteia-tessera hash crate (Keccak / SHA-2 now live in exactly one place), a deliberate three-tier quantica API (facade / hazmat / internal), pre-hash signatures (HashML-DSA / HashSLH-DSA), the SLH-DSA SHA-2 parameter sets, conformant external SLH-DSA sign/verify, and the first crates.io publication of the C-ABI (*_ffi) and WebAssembly (*_wasm) bindings. Embedded: the memory self-alloc allocator now builds on no-CAS targets (Cortex-M0, ESP32-C3). The public Rust API remains pre-1.0 and semver-unlocked.

Changed

  • Project hosting migrated from Codeberg to GitHub. Codeberg amended its Terms of Service (effective 2026-07-22) to bar projects “that consist predominantly of code written by generative AI tools”. Krypteia is developed in tandem with an AI assistant and documents that fact — CONTRIBUTING.md mandates a Co-Authored-By: trailer naming the model and context window on every AI-assisted commit, precisely so the Git history stays an auditable record of which tool produced which line. Rather than obscure that trail or argue the threshold, the project moved: the canonical repository is now https://github.com/cslashm/krypteia, CI runs on GitHub Actions (.github/workflows/, ported from .forgejo/), and the documentation pack is served from GitHub Pages. The doc domain (https://krypteia-rs.dev) is unchanged. The Codeberg repository is archived read-only so the links recorded in the published v0.1 crate metadata keep resolving.

  • quantica API re-tiered before the v0.2 publish (breaking vs 0.1, allowed on the pre-1.0 line and reserved by the v0.1 README). The public surface now has three deliberate tiers:

    • Facade (guaranteed): the typed MlKem/MlDsa/SlhDsa entry points, key/signature wrappers, params, rng, prehash, secret.

    • hazmat (new feature, off by default): the expert building-block tier — byte encodings, samplers, Decompose/hints, the SLH-DSA address/tweakable-hash surface, the raw-slice algorithm facades (ml_kem::kem, ml_dsa::dsa, slh_dsa::slh) and the deterministic *_internal CAVP entries. No stability, misuse-resistance or side-channel promise on this tier — it may change in any release. The KAT suites that drive the deterministic entries (ml_kem_kat, wycheproof, kat_extra) and krypteia-quantica-ffi opt in explicitly.

    • Internal (pub(crate)): ntt, the unauthenticated kpke component, WOTS+/XMSS/FORS/hypertree, the masking/shuffling countermeasures and the SHA-3 shims — no longer reachable, no longer a compatibility surface. This also removes the accidental exposure asymmetry between the ML-KEM and ML-DSA countermeasure modules.

  • arcana assumes its public arithmetic layer (additive): the SEC1 helpers are lifted to ecc::{compress_pubkey, decompress_pubkey} (both validate on-curve internally), and ecc::field / ecc::curve / rsa::bigint are now documented as a deliberate expert tier with an explicit contract — CT split (scalar_mul_point and point_double straight-line/CT; point_add/double_scalar_mul variable-time, public values only), the no-validation rule on raw point ops, and the variable-time warning on BigInt. Stability on this layer is best-effort pre-1.0.

Added

  • arcana::ecc::ed448 — full Ed448 (RFC 8032 §5.2). Edwards448 “Goldilocks” over GF(2⁴⁴⁸−2²²⁴−1), cofactor 4, SHAKE256-based hashing, 57-byte keys and 114-byte signatures. All three modes — pure Ed448, Ed448ctx (context string) and Ed448ph (prehash) — verified byte-exact against the RFC 8032 §7.4 / §7.5 test vectors. The secret-dependent scalar multiplication is constant-time from the first commit (ct_select double-and-add, mirroring the Ed25519 path). Closes the v0.1 Ed448-stub known limitation.

  • krypteia-tessera — new shared hash crate. no_std + alloc, zero runtime dependencies; the single home for every hash in the workspace. SHA-2 (224/256/384/512/512-224/512-256), SHA-3 (224/256/384/512), SHAKE128/256, cSHAKE128/256, MGF1, SHA-1, RIPEMD-160 and BLAKE2b/2s, behind two minimal traits — Digest (OUTPUT_LEN / BLOCK_LEN) and Xof. Every family is KAT-validated; hash compression is deliberately not constant-time (public-data inputs; secret-dependent comparison / zeroization stays in silentops).

  • SLH-DSA SHA-2 parameter sets (FIPS 205 §11.2). Six new parameter sets — Sha2_128s/f, Sha2_192s/f, Sha2_256s/f — alongside the existing SHAKE sets, behind a TweakableHash abstraction on the Params trait (ShakeFamily / Sha2Family). The SHA-2 instantiation uses SHA-256 for F/PRF and the compressed 22-byte ADRSᶜ, with SHA-256 at category 1 and SHA-512 at categories 3/5 for H_msg/PRF_msg/T_ℓ/H (FIPS 205 §11.2). Byte-exact against the NIST ACVP keyGen/sigGen/sigVer SHA-2 vectors.

  • Pre-hash signatures — HashML-DSA & HashSLH-DSA. New sign_prehash / verify_prehash on both schemes (FIPS 204 Alg 4/5, FIPS 205 Alg 23/25) with a shared PreHash selector covering the full approved set (SHA-224/256/384/512, SHA-512/224, SHA-512/256, SHA3-224/256/384/512, SHAKE128 → 256 bits, SHAKE256 → 512 bits). The OID DER byte strings are verified against NIST.FIPS.204 §5.4 / Algorithm 4 (full 11-byte TLV). Validated byte-exact against the ACVP pre-hash sigGen/sigVer groups, driven through the public typed API.

  • C-ABI and WebAssembly bindings published on crates.io, under the krypteia-* namespace like every other published crate: krypteia-quantica-ffi, krypteia-arcana-ffi, krypteia-tessera-ffi (staticlib + cdylib + hand-written C headers) and krypteia-quantica-wasm, krypteia-arcana-wasm, krypteia-tessera-wasm (wasm-bindgen) flip from publish = false to published, each exercised by a runtime harness (the C test_*.c suites via tools/ffi-tests.sh, and the Node wasm-bindgen tests). Their directories keep the bare names (quantica_ffi/) and each pins [lib] name to the bare form, so the built artefacts remain libquantica_ffi.{a,so} and the C headers, link recipes and wasm-pack invocations are unchanged. As pre-1.0 crates their ABI/JS surface may still change in a minor release.

Changed

  • BREAKING — SLH-DSA public sign / verify now implement the FIPS 205 external algorithm. They take a context string ctx and apply the domain-separation wrap M′ = 0x00 len(ctx) ctx M before the internal algorithm, so signatures interoperate with conformant external SLH-DSA verifiers; previously they signed the raw message via the internal algorithm (non-conformant). This changes the signature bytes for a given (key, message) — a deliberate conformance break, acceptable pre-1.0 (like the 256f fix). Pass ctx = b"" for the no-context case. ML-DSA already exposed the external ctx path. Adds SlhDsaError::ContextTooLong (raised when |ctx| > 255).

  • BREAKING — the FIPS internal functions are now crate-private. ML-DSA dsa::{keygen,sign,verify}_internal and SLH-DSA slh::slh_{keygen,sign,verify}_internal are pub(crate), and the redundant MlDsa / SlhDsa *_internal inherent methods are removed: the only public way to sign is the conformant external path. The internal functions bypass domain separation and were a public footgun; a designed low-level / custom-M′ wrapper may be re-introduced on real demand (roadmap). ML-KEM internals are unchanged (a KEM has no M′/ctx layer). The ACVP internal-interface vectors moved to in-crate #[cfg(test)] KATs (ml_dsa::acvp_internal, slh_dsa::acvp_internal).

  • arcana hash functions relocated into krypteia-tessera. arcana::hash is now a thin façade that re-exports the shared types; the per-algorithm module paths (arcana::hash::sha256::Sha256, …) and arcana’s public Hasher / Xof traits are unchanged, bridged to the crate by two blanket impls (impl<T: tessera::Digest> Hasher for T, impl<T: tessera::Xof> Xof for T). No algorithm or behavioural change: the implementations were moved verbatim and re-validated against the unchanged NIST CAVP hash KATs, NIST ACVP and Google Wycheproof corpora (arcana lib 291/291, CAVP 11/11, ACVP 16/16, Wycheproof 6/6). Removes ~2.3k lines of SHA-2 / SHA-3 duplication on the arcana side.

  • quantica Keccak relocated into krypteia-tessera too. quantica::sha3 is now a shim re-exporting tessera::sha3::KeccakState (+ the FIPS 202 rate constants); the ML-KEM / ML-DSA / SLH-DSA sha3 wrappers and call sites are unchanged. With this, Keccak exists in exactly one place in the workspace. Both cores are plain, data-oblivious Keccak-f[1600], so no side-channel property changes; an A/B throughput harness (quantica_bench keccak_bench) measured the two at parity. Byte-exact: ML-KEM / ML-DSA KAT, SLH-DSA KAT (incl 256f), and the M0 thumbv6m no_std build all green on the shared core.

Removed

  • Third-party kat_MLKEM_*.rsp vectors (from post-quantum-cryptography/KAT) and their tests. Their ML-KEM key derivation diverges from the authoritative NIST ACVP vectors — our ACVP-validated keygen_internal reproduces their pk with neither (d, z) nor (z, d) — so they are not a reliable FIPS 203 oracle (likely a pre-final Kyber round-3 derivation, missing the FIPS 203 G(d k) domain byte). ML-KEM conformance remains fully covered by the NIST ACVP suite and Wycheproof. Full rationale is documented in quantica/tests/kat_extra.rs.

Fixed

  • SLH-DSA-SHAKE-256f produced non-conformant signatures. The idx_tree mask computed 1u64 << (H - H'), which overflows for the 256f parameter set (H - H' = 64): in release builds the shift wraps to 1 << 0, forcing idx_tree to 0 and yielding signatures that are self-consistent but non-conformant to FIPS 205. Fixed at both the sign and verify sites (quantica/src/slh_dsa/slh.rs); the previously #[ignore]-d 256f siggen/sigver KATs now pass. Only 256f was affected (every other parameter set has H - H' 63).

  • memory self-alloc now builds on no-CAS embedded targets. The self-alloc spin-lock used AtomicBool::compare_exchange_weak, which core does not expose on targets without hardware CAS, so krypteia-memory --features self-alloc failed to compile (E0599) on Cortex-M0 (thumbv6m) and the ESP32-C3-class RV32 imc — the M0/ESP32-C3 support advertised since v0.1. Replaced with a per-target interrupt-masking critical section (ARM PRIMASK, RISC-V mstatus.MIE); CAS-capable targets keep the lock-free spin-lock. Branch-free asm, no public API change.

0.1.0 - 2026-06-12

First public release of the krypteia cryptographic workspace. Four crates ship on crates.io under the krypteia-* namespace: krypteia-silentops, krypteia-memory, krypteia-quantica, and krypteia-arcana. Apache-2.0 across the workspace.

The krypteia-* namespace prefix was adopted because the bare names memory and arcana were already taken on crates.io by third parties at first publish attempt. Every published crate sets [lib] name = "<short>" so consumers use silentops::*, use memory::*, use quantica::*, use arcana::* exactly as they would have with the bare names; the only place the namespaced name appears is the Cargo.toml [dependencies] line and on crates.io / docs.rs URLs.

Added — quantica (post-quantum cryptography)

  • ML-KEM (FIPS 203, Key Encapsulation Mechanism) — pure-Rust implementation, all parameter sets (MlKem512, MlKem768, MlKem1024), typed Zeroize-on-Drop key wrappers.

  • ML-DSA (FIPS 204, Digital Signature Algorithm) — pure-Rust implementation, all parameter sets (MlDsa44, MlDsa65, MlDsa87), with per-iteration mask refresh in the rejection loop (T1-A), Hermelink 2025/276 audit annex on ml_dsa::masked (T1-B).

  • SLH-DSA (FIPS 205, Stateless Hash-Based Signatures) — pure-Rust implementation, SHAKE variants (Sha2-128f/s, Shake-128f/s, Shake-192f/s, Shake-256f/s), with FORS recompute-and-compare redundancy (T1-C, anti-grafting Castelnovi 2018), full-tree streaming FORS sign (T1-D, anti-template Kannwischer 2018), digest → FORS-indices integrity check (T1-E, anti-fault), and a constant-time fors_pk_from_sig_ct (T1-F).

  • First-order DPA masking + shuffled NTT on both ML-KEM and ML-DSA secret paths (sca-protected Cargo feature, on by default).

  • Iterative BDS FORS treehash (256 KiB → 448 B per call) and streaming signature output (one allocation, *_into variants throughout) to fit the M0 baseline RAM budget.

  • Seven RAM-reduction features for ML-DSA bringing the M0 Sign stack from 179 KB → ~17 KB peak.

  • no_std build target support; cross-compile recipes in quantica/README.md for thumbv6m-none-eabi, thumbv7em-none-eabihf, thumbv8m.main-none-eabihf, riscv32imc-unknown-none-elf.

Added — arcana (classical cryptography)

  • RSA — PKCS#1 v1.5, PSS (with Sha256 / Sha384 / Sha512 / SHA-3 family digests), OAEP.

  • ECDSA / ECDH — NIST P-256 / P-384 / P-521, SECG secp256k1, Brainpool families (P-256 / P-384 / P-512). CT hardening on scalar_mul_point (Montgomery ladder, branchless point_add_ct, black_box shielding on field_* masks).

  • EdDSA — Ed25519 (RFC 8032 §5.1).

  • ECDH — X25519 and X448.

  • AES — 128 / 192 / 256 with ECB, CBC, CTR, GCM, CCM (RFC 3610), XTS (IEEE 1619) modes.

  • ChaCha20 and ChaCha20-Poly1305 (RFC 8439), XChaCha20-Poly1305 (24-byte nonce extension).

  • DES / 3DES (legacy use only — flagged in the per-crate Known limitations).

  • Hashes: SHA-1, SHA-2 family (224 / 256 / 384 / 512 / 512-truncated), SHA-3 family (224 / 256 / 384 / 512 + SHAKE128/256, cSHAKE128/256), BLAKE2b / BLAKE2s, RIPEMD-160.

  • MACs: HMAC, CMAC.

  • Optional RustCrypto trait bridges (digest, cipher, signature) behind the rust-crypto-traits Cargo feature.

  • no_std build target support.

Added — silentops (side-channel countermeasure toolkit)

  • Constant-time primitives — ct::ct_eq, ct::ct_select_*, ct::ct_zeroize, ct::ct_copy with five architecture- specific inline-assembly backends gated on target_arch + target_feature + Cargo feature:

    • asm-x86_64 — x86_64 inline asm;

    • asm-aarch64 — AArch64 with csel / csinv;

    • asm-thumbv7 — ARMv7-M Thumb2 IT blocks (Cortex-M3 / M4 / M33);

    • asm-thumbv6m — ARMv6-M no-IT branchless (Cortex-M0 / M0+);

    • asm-riscv32 — RV32I branchless (ESP32-C3 / C6 / H2).

  • ct_grind — Valgrind memcheck client-request helpers (poison / unpoison) with zero-cost no-op fallback on non-Linux x86_64 / aarch64 targets.

  • verify — dudect-style timing-leakage detector (Welch t-test, std-only) refactored as a reusable library.

Added — memory (TLSF allocator)

  • Two allocator backends gated by Cargo feature:

    • os-alloc (default) — forwards to the platform malloc / free;

    • self-alloc — TLSF allocator over a caller-provided RAM block, single-init, no std.

  • global-alloc feature for the FFI consumers (arcana_ffi, future quantica_ffi) to register as #[global_allocator].

Added — Cross-architecture validation infrastructure (T3-A)

Workspace-internal, exercised on every push:

  • tools/qemu-user-tests.sh — workspace lib tests on three Linux triplets via cross + qemu-user: aarch64-unknown- linux-gnu, armv7-unknown-linux-gnueabihf, riscv64gc-unknown-linux-gnu. Pinned to cross 0.2.5 + ghcr.io/cross-rs/*:0.2.5 Docker images.

  • tools/qemu-system-tests.shtests-embedded smoke test on four bare-metal targets: riscv32imc-unknown-none-elf (ESP32-C3), riscv32imac-unknown-none-elf (ESP32-C6 / H2), thumbv6m-none-eabi (Cortex-M0, qemu microbit), thumbv7em-none-eabihf (Cortex-M4F, qemu mps2-an386).

  • tools/qemu-vector-tests.sh + tools/vector-runner/ — host↔guest semihosting vector-streaming protocol that runs validation corpora through the bare-metal vector_runner bin without compiling vectors into the binary. Generator and consumer share the wire format via tests_embedded::protocol.

  • Codeberg Forgejo Actions workflow (.forgejo/workflows/qemu-cross-tests.yml) running all three layers in parallel on every push to main and every pull request.

Added — Documentation pack and CI

  • Sphinx documentation pack (gendoc.sh all) — per-crate README rendered with the per-crate doc/sca/ side-channel annex inlined as a nested TOC under each crate’s chapter VIII subsection 5; cross-crate sidebar entries for silentops and memory; separate Governance sidebar group for CONTRIBUTING and SECURITY.

  • Continuous publish to krypteia-rs.dev via .forgejo/workflows/doc.yml on every push to main.

  • Workspace governance documents linked from the root README:

    • CONTRIBUTING.md — contribution policy, including the AI-assist Co-Authored-By: trailer requirement;

    • SECURITY.md — security maintenance process and full responsible-disclosure policy (90-day coordinated disclosure window default, MITRE CNA process for CVE assignment, safe-harbour clause for good-faith research).

Conventions and workspace shape adopted in v0.1

  • Rust edition 2024 (MSRV 1.85) across the workspace.

  • [workspace.package] factorisation of release metadata (license, repository, homepage, authors, version, edition, rust-version) — inherited via <field>.workspace = true.

  • [workspace.dependencies] factorisation of inter-crate path-deps with version = "0.1.0" on each so cargo publish accepts them.

  • publish = false on quantica_ffi, arcana_ffi, quantica_wasm, quantica_bench, arcana_bench, arcana_wasm, tests-embedded, tools/vector-runner — these are workspace-internal previews / placeholders; the C ABI and JS surface stabilise in v0.2.

  • Strict branch policy enforced by tools/hooks/pre-commit: main, master, pqc-dev are read-only, dev work happens on dev/* / doc/* / infra/* / release/* branches.

Known limitations carried into v0.1

These are documented honestly so a v0.1 consumer or external reviewer can size them up before depending on the workspace:

  • arcana::ecc::eddsa::Ed448 — stub. Full implementation deferred to v0.2 against RFC 8032 §5.2.

  • Five #[ignore]-d KAT tests under quantica/tests/ (three ML-KEM RSP + two SLH-DSA SHAKE-256f) — root cause investigation deferred to v0.2 evaluation prep.

  • thumbv8m.main-none-eabihf bare-metal target — wired in tests-embedded/memory/mps2-an505.x, tools/qemu-system-tests.sh, and .cargo/config.toml, but currently disabled in the active matrix due to an upstream rustc 1.96 + cortex-m-rt 0.7.5 linker issue that emits an empty ELF. silentops’s asm-thumbv7 backend coverage is preserved via the thumbv7em path (M4F and M33 share the asm backend).

  • 45 unsafe-op-in-unsafe-fn warnings on arcana_ffi and ~69 not_unsafe_ptr_arg_deref clippy errors on quantica_ffi — edition 2024 ripple on the FFI surface. These are workspace-internal in v0.1 (publish = false) and addressed when the C ABI is reworked for v0.2.

  • Codeberg shared-runner CI is not green for v0.1.0 — two distinct image-compat issues block the workflows on the free shared-runner pool: (a) the cross-based qemu-user matrix in .forgejo/workflows/qemu-cross-tests.yml requires Docker-in-Docker, which the pool does not expose; (b) the doc.yml Sphinx build calls rsync, which is not present in the runner image’s apt set. The bare-metal qemu-system and qemu-vector matrices use no Docker and need no additional packages, but were not exercised on the pool for v0.1.0 because the YAMLs are bundled in a single workflow with the broken qemu-user matrix. Local validation (./tools/qemu-{user,system,vector}-tests.sh, ./tools/ctgrind.sh, ./gendoc.sh all) covers the same scope the workflows would have. The v0.1.0 long-form Sphinx doc pack was deployed to Codeberg Pages manually from a local ./gendoc.sh all run, not via the doc.yml workflow. Workflow migration to non-Docker tooling (qemu-user-static + binfmt-misc for the cross-arch matrix, apt-get rsync for the doc build) lands in v0.2.

Initial public release commit

The b31fdbd “Initial public release of krypteia” commit (2026-03-13) seeded the public history as an orphan commit, deliberately disconnected from earlier internal exploration branches that referenced third-party non-public material under NDA. All work in this CHANGELOG is reachable from that orphan root.