# Changelog All notable changes to this project are documented in this file. The format is based on [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/), and the workspace follows [Semantic Versioning 2.0.0](https://semver.org/). The pre-`1.0` line ships breaking changes in **minor** version bumps (`0.1.x` → `0.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 (`; 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 , CI runs on GitHub Actions (`.github/workflows/`, ported from `.forgejo/`), and the documentation pack is served from GitHub Pages. The doc domain () 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 Hasher for T`, `impl 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`](https://crates.io/crates/krypteia-silentops), [`krypteia-memory`](https://crates.io/crates/krypteia-memory), [`krypteia-quantica`](https://crates.io/crates/krypteia-quantica), and [`krypteia-arcana`](https://crates.io/crates/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 = ""` 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.sh` — `tests-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](https://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 `.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. [Unreleased]: https://github.com/cslashm/krypteia/compare/v0.1.0...HEAD [0.1.0]: https://github.com/cslashm/krypteia/releases/tag/v0.1.0