Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 133 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

| Property | Value |
|----------|-------|
| Applies to Release | 3.1.0 |
| Last Updated | 2026-05-16 |
| Applies to Release | 3.2.0 |
| Last Updated | 2026-05-20 |
| Classification | Public |
| Maintainer | Steel Security Advisors LLC |

Expand All @@ -19,7 +19,138 @@ All notable changes to AMA Cryptography will be documented in this file. The for

## [Unreleased]

(empty — every entry from the previous `[Unreleased]` section moved to
`[3.2.0]` below.)

---

## [3.2.0] - 2026-05-20

### Added
- **Per-slot SIMD auto-tune with file-based cross-process cache
(dispatch surgical close-out).** `src/c/dispatch/ama_dispatch.c`
benches each SIMD slot (`keccak_f1600`, `keccak_f1600_x4`,
`kyber_ntt` / `invntt`, `dilithium_ntt` / `invntt`) **independently**
against its scalar reference and reverts the slot pointer
per-bench when the SIMD path regresses past the 10 % hysteresis
band. Replaces the prior one-bench-drives-everything design that
could discard a working AVX-512 4-way kernel whenever the AVX2
single-state kernel regressed on a noisy host
(`BUG_pr-review-job-f0260c65de73482bb856b1b86b90eda3_0001`):
- The `keccak_f1600_x4` bench uses an inline 4× single-state
fold as its scalar baseline (rather than re-entering
`ama_keccak_f1600_x4_generic`, which would deadlock the
currently-running `pthread_once`), and the verdict is acted on
alone — never lockstepped with the single-state verdict.
- The `kyber_ntt` / `kyber_invntt` / `dilithium_ntt` /
`dilithium_invntt` benches use new `ama_kyber_ntt_generic_ref`
/ `ama_kyber_invntt_generic_ref` /
`ama_dilithium_ntt_generic_ref` /
`ama_dilithium_invntt_generic_ref` symbols (extracted from
the inline scalar paths in `src/c/ama_kyber.c` and
`src/c/ama_dilithium.c`). The production fallback path in
`poly_ntt` / `poly_invntt` / `dil_ntt_cached` /
`dil_invntt_cached` now delegates to the same scalar helper
the bench measures — single source of truth for the scalar
reference.
- `AMA_DISPATCH_CACHE_FILE=<path>` (declared in
`include/ama_dispatch.h`) is the new opt-in env-var contract
for cross-process auto-tune caching. When set, the per-slot
verdict is written to <path> after a successful bench;
subsequent processes with the same env var and a matching
CPU-feature fingerprint load the verdict and skip the
~10 K-Keccak-iteration microbench entirely. Cache key is a
deterministic string of `arch_name` + each runtime CPU-feature
probe result (`avx2`, `avx512f`, `avx512_keccak_bundle`,
`aes_ni`, `pclmulqdq`, `vaes_aesgcm_bundle`, `arm_aes`,
Comment on lines +64 to +65
`arm_pmull`) — kernel upgrades / microcode changes that shift
any flag invalidate the cache automatically. Default
(env unset) does no file I/O. Bypassed entirely when
`AMA_DISPATCH_NO_AUTOTUNE=1` is also set.
- Lockstep tie preserved (carved out): the single-state
`keccak_f1600` verdict still drives the `sha3_256` and
`kyber_poly_{add,sub,reduce}` slots in lockstep, because the
SVE2 `sha3_256` wrapper embeds `ama_keccak_f1600_sve2` and
the three `kyber_poly_*` slots share the SVE2 codegen tier
with no independent kernel. Documented at the apply-verdicts
block in `dispatch_init_internal`.
- **`ama_keypair_generate(AMA_ALG_ED25519)` wired through to the
Ed25519 backend (functional-completeness close-out).**
`src/c/ama_core.c::ama_keypair_generate` previously returned
`AMA_ERROR_NOT_IMPLEMENTED` for `AMA_ALG_ED25519`; it now draws a
32-byte seed from the platform CSPRNG (`ama_randombytes`) and
delegates to `ama_ed25519_keypair` (which honours the AMA
convention that the caller supplies the seed in
`secret_key[0..31]`). `ama_sign` and `ama_verify` gained matching
`AMA_ALG_ED25519` arms so the generated keypair is usable through
the algorithm-agnostic API end-to-end. INVARIANT-6 preserved:
the secret-key buffer is `ama_secure_memzero`-scrubbed if the
CSPRNG draw fails or the Ed25519 keypair derivation returns an
error. Public-key buffer scrubbed on the same error path so a
partial public key cannot leak.
- **`AMA_DISPATCH_CACHE_FILE` env-var contract documented in
`include/ama_dispatch.h`.** Full opt-in semantics, fingerprint
composition, cache-miss / cache-hit / NO_AUTOTUNE precedence,
and forward-compat file-format rules — see the "Cross-process
auto-tune cache" header block.

### Changed
- **`tests/c/test_dudect.c::test_consttime_memcmp` — symmetric
setup discipline.** Pre-fix, class 0 did `random_bytes(a) +
memcpy(b,a)` while class 1 added an extra `rand()` draw and an
in-place XOR on `b`. Those pre-timer asymmetries (libc-call
frequency, branch-predictor state, cache line provenance of the
XOR write) bled into the timing window and produced a +12σ
false-positive on the CI dudect run — the underlying
`ama_consttime_memcmp` is byte-by-byte branchless in source
(`src/c/ama_consttime.c`). Post-fix, both classes compute
`b_equal = a` and `b_diff = a with one bit flipped at a random
position` BEFORE the class selection, and a pointer-select-out-of-
timer chooses which buffer is fed to the constant-time compare.
Reading on a contended Linux runner dropped from t = +12.36 to
t = -1.82 (well below the 4.5 threshold). Same setup-symmetry
pattern the FROST / Kyber-decaps / Dilithium-sign lanes already
use.
- **`tests/c/test_dudect.c::test_frost_scalar_negate_midrange` —
memory-class symmetry.** Pre-fix, the class-0 reference scalar
was stack-resident (a `memset`-zeroed local array) while the
class-1 reference scalar was read directly from
`SCALAR_NEGATE_MID` in `.rodata`. The cache-line provenance
asymmetry surfaced as a structural −6σ delta in the Welch t-test
even though `ama_frost_test_scalar_negate` is byte-by-byte
branchless (`src/c/ama_frost.c`). Post-fix, the mid-range scalar
is staged into a stack buffer at function entry so both inputs
live in the same memory class; pointer-select stays outside the
timer. Reading dropped from t = -6.70 to t = +1.86. Documented
at the lane header so future readers see the prior triage.
- **`src/c/ama_kyber.c` and `src/c/ama_dilithium.c` — scalar NTT
paths extracted as named static helpers.** `poly_ntt` /
`poly_invntt` (Kyber) and `dil_ntt_cached` / `dil_invntt_cached`
(Dilithium) now delegate their scalar fallback to
`kyber_ntt_scalar` / `kyber_invntt_scalar` / `dil_ntt_scalar` /
`dil_invntt_scalar` (each `static`-linkage, matching the
`ama_kyber_ntt_fn` / `ama_dilithium_ntt_fn` signatures). The
same helpers are wrapped by the new `ama_*_generic_ref` extern
symbols that the dispatch auto-tune microbenches. Single
source of truth: the algorithm has not moved, only its scope —
pinned by every existing ML-KEM-1024 / ML-DSA-65 KAT.

### Documentation
- **`CONSTANT_TIME_VERIFICATION.md` — "Harness Setup-Symmetry
Discipline" subsection.** Codifies the three-rule pattern
(identical setup work / same-memory-class staged inputs /
pointer-select-out-of-timer) that future dudect lanes must follow,
with a forward pointer to the two v3.2.0 hardenings.
- **`CHANGELOG.md` — release line for v3.2.0.** Moves every entry
from the previous `[Unreleased]` section into `[3.2.0] -
2026-05-20`. No silent additions — the dispatch surgical
close-out, Ed25519 wiring, and dudect setup hardenings all land
here.

### Earlier in the 3.2.0 cycle (carried forward from prior
`[Unreleased]` section — full text below)

### Added (carried forward from the prior `[Unreleased]` section)
- **Tagged-release pipeline (audit Issue 1).** New
`.github/workflows/release.yml` runs cibuildwheel across Linux x86-64,
Linux ARM64, macOS x86-64, macOS arm64, and Windows AMD64 for
Expand Down
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
cmake_minimum_required(VERSION 3.15)

project(AmaCryptography
VERSION 3.1.0
VERSION 3.2.0
DESCRIPTION "Quantum-Resistant Cryptographic Protection System"
LANGUAGES C CXX
)
Expand Down
36 changes: 34 additions & 2 deletions CONSTANT_TIME_VERIFICATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

| Property | Value |
|----------|-------|
| Document Version | 3.1.0 + Unreleased |
| Last Updated | 2026-05-16 |
| Document Version | 3.2.0 |
| Last Updated | 2026-05-20 |
| Classification | Public |
| Maintainer | Steel Security Advisors LLC |

Expand Down Expand Up @@ -123,6 +123,38 @@ Overall: PASS - No timing leakage detected

**Note**: Environmental factors such as CPU frequency scaling, interrupts, and cache effects can cause false positives. Run the test multiple times and consider disabling CPU frequency scaling for more accurate results.

### Harness Setup-Symmetry Discipline

Two lanes in `tests/c/test_dudect.c` (`test_consttime_memcmp` and
`test_frost_scalar_negate_midrange`) were hardened in v3.2.0 against a
false-positive class identified on noisy CI runners. The underlying
primitives (`ama_consttime_memcmp` and FROST `scalar_negate`) are
byte-by-byte branchless in source, but the harnesses fed them inputs
through asymmetric setup paths — class 1 in `test_consttime_memcmp`
made an extra `rand()` call and one extra branch-conditional write
before the timer started, and `test_frost_scalar_negate_midrange`
served class-0 inputs from a stack array while class-1 came from
`.rodata`. The pre-timer asymmetries (branch-predictor state, cache
line provenance, libc call frequency) bled into the timed window and
surfaced as ~+12σ and ~−6σ false-positive readings respectively.

The post-fix pattern, codified at the top of each lane in
`tests/c/test_dudect.c`:

1. Perform identical setup work for both classes (same `rand()`
draws, same memcpy count, same conditional writes — driven by an
index that is independent of `class_idx`).
2. Stage every reference input into the same memory class (typically
the local stack frame) so the kernel reads them through equivalent
cache paths.
3. Pointer-select between the two staged inputs OUTSIDE the timing
region. The timed window contains exactly one indirect call with
no class-correlated control flow.

Future dudect lanes should follow the same discipline. Helper
patterns: a `b_equal` / `b_diff` pair for compare-style primitives, a
single stack-staged reference for scalar-input primitives.

## ctgrind/Valgrind Verification

For more rigorous verification, you can use ctgrind (constant-time grind) with Valgrind:
Expand Down
2 changes: 1 addition & 1 deletion ama_cryptography/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
import sys as _sys
from typing import TYPE_CHECKING, Any

__version__ = "3.1.0"
__version__ = "3.2.0"
__author__ = "Andrew E. A., Steel Security Advisors LLC"

# Windows DLL search-path registration (Python 3.8+).
Expand Down
2 changes: 1 addition & 1 deletion docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ FROM ubuntu:22.04

LABEL maintainer="Steel Security Advisors LLC <steel.sa.llc@gmail.com>"
LABEL description="AMA Cryptography - Quantum-Resistant Cryptographic Protection System"
LABEL version="3.1.0"
LABEL version="3.2.0"

# Install runtime dependencies only
# INVARIANT-1: libssl3 is NOT installed — AMA's native C primitives do not
Expand Down
2 changes: 1 addition & 1 deletion docker/Dockerfile.c-api
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,6 @@ CMD ["sh", "-c", "mkdir -p /output/lib /output/include && \
# Labels
LABEL org.opencontainers.image.title="AMA Cryptography C API"
LABEL org.opencontainers.image.description="Reproducible C library build with native PQC (FIPS 203/204/205)"
LABEL org.opencontainers.image.version="3.1.0"
LABEL org.opencontainers.image.version="3.2.0"
LABEL org.opencontainers.image.vendor="Steel Security Advisors LLC"
LABEL org.opencontainers.image.licenses="Apache-2.0"
4 changes: 2 additions & 2 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@
project = "AMA Cryptography"
copyright = "2025-2026, Steel Security Advisors LLC"
author = "Andrew E. A."
version = "3.1.0"
release = "3.1.0"
version = "3.2.0"
release = "3.2.0"

# General configuration
extensions = [
Expand Down
4 changes: 2 additions & 2 deletions include/ama_cryptography.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ extern "C" {
* ============================================================================ */

#define AMA_CRYPTOGRAPHY_VERSION_MAJOR 3
#define AMA_CRYPTOGRAPHY_VERSION_MINOR 1
#define AMA_CRYPTOGRAPHY_VERSION_MINOR 2
#define AMA_CRYPTOGRAPHY_VERSION_PATCH 0
#define AMA_CRYPTOGRAPHY_VERSION_STRING "3.1.0"
#define AMA_CRYPTOGRAPHY_VERSION_STRING "3.2.0"

/* ============================================================================
* ALGORITHM IDENTIFIERS
Expand Down
34 changes: 34 additions & 0 deletions include/ama_dispatch.h
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,40 @@ AMA_API const char *ama_impl_level_name(ama_impl_level_t level);
*/
AMA_API const char *ama_aes_gcm_active_backend(void);

/* ============================================================================
* Cross-process auto-tune cache (opt-in)
* ============================================================================
*
* `AMA_DISPATCH_CACHE_FILE=<path>` — when set in the environment before
* the first `ama_dispatch_init()` call, the per-slot auto-tune
* microbench writes its regressed/kept verdict for each SIMD slot to
* <path>, and subsequent processes with the same env var (and matching
* CPU-feature fingerprint) skip the microbench entirely and apply the
* cached verdict. Removes the ~10K-Keccak-iteration startup latency
* on warm hosts without sacrificing the per-host accuracy of the
* regression heuristic.
*
* Cache key — a deterministic string built from `arch_name` plus the
* runtime CPU-feature probe results (`avx2`, `avx512f`,
* `avx512_keccak_bundle`, `aes_ni`, `pclmulqdq`, `vaes_aesgcm_bundle`,
* `arm_aes`, `arm_pmull`). A kernel upgrade or microcode change that
* shifts any flag invalidates the cache automatically — no manual
* flush. Mismatched fingerprints are treated as a cache miss; the
* bench runs and rewrites the file.
*
* Default (env unset) — no file I/O on this code path, strictly opt-in.
* Distribution packagers can ship a pre-warmed cache in /etc or under
* `$XDG_CACHE_HOME/ama-cryptography/` and have downstream processes
* pick it up by exporting `AMA_DISPATCH_CACHE_FILE` in their service
* wrappers. See `src/c/dispatch/ama_dispatch.c::dispatch_cache_save`
* for the (text, one key=value per line) file format and forward-
* compatibility behaviour.
*
* The cache is bypassed when `AMA_DISPATCH_NO_AUTOTUNE=1` is set — the
* opt-out env var takes precedence and the bench is skipped without
* any cache read or write.
*/

/* ============================================================================
* Per-slot dispatch isolation (audit Issue 3 close-out / INVARIANT-12)
* ============================================================================
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "ama-cryptography"
version = "3.1.0"
version = "3.2.0"
description = "Quantum-resistant cryptographic protection system for helical mathematical Omni-Codes"
readme = "README.md"
license = {text = "Apache-2.0"}
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ def _check_cmake_version() -> None:
np = None

# Configuration
VERSION = "3.1.0"
VERSION = "3.2.0"
USE_CYTHON = CYTHON_AVAILABLE and not os.getenv("AMA_NO_CYTHON")
USE_C_EXTENSIONS = not os.getenv("AMA_NO_C_EXTENSIONS")
DEBUG = bool(os.getenv("AMA_DEBUG"))
Expand Down
Loading
Loading