# krypteia-arcana — Classical Cryptography for the krypteia workspace
Pure-Rust implementations of the classical cryptographic primitives
(hashes, symmetric ciphers, MACs, RSA, ECC, EdDSA, Montgomery DH),
sharing the side-channel countermeasure toolkit `silentops` with the
post-quantum crate `quantica`. The crate is the "classical" half of
the [krypteia](https://github.com/cslashm/pqc) workspace.
## Design rules
The crate inherits the krypteia workspace design rules:
1. **Pure Rust, zero external crates** — only `core` (and `alloc`);
`std` is optional behind a feature flag. The only workspace
dependency is `silentops` for shared CT primitives and timing-leak
verification.
2. **Embedded-friendly** — caller-provided buffers, no hidden heap
allocation in the hot path. Target devices: secure elements,
STM32 (Cortex-M0/M4/M33), RISC-V (ESP32-C3, …). `no_std` is on the
roadmap.
3. **Side-channel hardened** — timing-constant comparisons,
deterministic ECDSA nonces (RFC 6979), no secret-dependent
branches on the CT-critical paths. AES S-box is table-based
(known cache surface, documented below). ECC scalar multiplication
uses a CT Montgomery ladder hardened against branch reintroduction
by the optimizer.
4. **Validated** — every algorithm is tested against pinned RFC /
NIST / FIPS reference vectors, plus three external corpora
(Wycheproof, NIST CAVP, NIST ACVP).
5. **C FFI-exposable** — the companion crate `arcana_ffi` exports
~20 `extern "C"` functions with a C header at
`arcana_ffi/include/arcana.h`.
## Algorithms
Arcana exposes six primitive families, all in `src/`:
### Hash functions
| SHA-1 | 160 b | `hash::sha1::Sha1` *(legacy)* | FIPS 180-4 |
| SHA-224 | 224 b | `hash::sha224::Sha224` | FIPS 180-4 |
| SHA-256 | 256 b | `hash::sha256::Sha256` | FIPS 180-4 |
| SHA-384 | 384 b | `hash::sha384::Sha384` | FIPS 180-4 |
| SHA-512 | 512 b | `hash::sha512::Sha512` | FIPS 180-4 |
| SHA-512/224 | 224 b | `hash::sha512_trunc::Sha512_224` | FIPS 180-4 |
| SHA-512/256 | 256 b | `hash::sha512_trunc::Sha512_256` | FIPS 180-4 |
| SHA3-224 | 224 b | `hash::sha3::Sha3_224` | FIPS 202 |
| SHA3-256 | 256 b | `hash::sha3::Sha3_256` | FIPS 202 |
| SHA3-384 | 384 b | `hash::sha3::Sha3_384` | FIPS 202 |
| SHA3-512 | 512 b | `hash::sha3::Sha3_512` | FIPS 202 |
| SHAKE128 | XOF | `hash::sha3::Shake128` | FIPS 202 |
| SHAKE256 | XOF | `hash::sha3::Shake256` | FIPS 202 |
| cSHAKE128 | XOF | `hash::sha3::CShake128` | NIST SP 800-185 |
| cSHAKE256 | XOF | `hash::sha3::CShake256` | NIST SP 800-185 |
| BLAKE2b | 1-512 b| `hash::blake2::Blake2b` | RFC 7693 |
| BLAKE2s | 1-256 b| `hash::blake2::Blake2s` | RFC 7693 |
| RIPEMD-160 | 160 b | `hash::ripemd160::Ripemd160` *(legacy)* | ISO/IEC 10118-3 |
### Symmetric ciphers and modes
| AES-128 / 192 / 256 | `cipher::aes` | FIPS 197 |
| DES, Triple-DES (EDE) | `cipher::des` | FIPS 46-3 |
| ECB / CBC / CTR / GCM | `cipher::modes` | NIST SP 800-38A/D |
| AES-CCM AEAD | `cipher::ccm` | NIST SP 800-38C, RFC 3610 |
| AES-XTS disk encryption | `cipher::xts` | IEEE 1619 |
| ChaCha20 stream cipher | `cipher::chacha20` | RFC 8439 |
| Poly1305 one-time MAC | `cipher::poly1305` | RFC 8439 |
| ChaCha20-Poly1305 AEAD | `cipher::chacha20poly1305` | RFC 8439 |
| XChaCha20-Poly1305 AEAD (24B nonce) | `cipher::xchacha20poly1305` | draft-irtf-cfrg-xchacha |
| Streaming Cipher ctx | `cipher::ctx` | — |
The streaming `Cipher` ctx wraps AES / DES / 3DES with ECB / CBC / CTR
behind `init / update / finalize`, with 5 padding schemes: `None`,
`Pkcs7`, `Iso9797M1` (zero), `Iso9797M2` (ISO 7816-4), `AnsiX923`.
AEAD modes stay function-oriented (no unverified plaintext release).
### Message authentication codes (MACs)
| HMAC | SHA-1, SHA-256/384/512, SHA3-256/384/512, RIPEMD-160 | RFC 2104, FIPS 198-1 |
| CMAC | AES-128/192/256, Triple-DES | NIST SP 800-38B, RFC 4493 |
| KMAC | KMAC128, KMAC256 | NIST SP 800-185 |
| GMAC | AES-128/192/256 | NIST SP 800-38D |
Three init variants: `init(key)`, `init_kmac(key, custom)`,
`init_with_nonce(key, nonce)`. `verify` accepts truncated tags,
constant-time comparison. Poly1305 excluded (one-time MAC, unsafe to
reuse via an "init then update again" object).
### RSA
| PKCS#1 v1.5 enc+sig | `rsa::pkcs1` | RFC 8017 §7-8 |
| OAEP encryption | `rsa::oaep` | RFC 8017 §7.1 |
| RSASSA-PSS signature | `rsa::pss` | RFC 8017 §8.1 |
Signatures support 8 hash functions: SHA-1, SHA-256/384/512,
SHA3-256/384/512, RIPEMD-160.
### Elliptic curve cryptography
Seven short-Weierstrass curves behind a unified `Curve` trait
(keygen, ECDSA RFC 6979, ECDSA random-nonce, ECDH, SEC1
compression / decompression, DER signatures):
| `P256` | NIST P-256 | FIPS 186-5 |
| `P384` | NIST P-384 | FIPS 186-5 |
| `P521` | NIST P-521 | FIPS 186-5 |
| `Secp256k1` | secp256k1 | SEC 2 §2.4.1 |
| `BrainpoolP256r1` | Brainpool 256-bit | RFC 5639 |
| `BrainpoolP384r1` | Brainpool 384-bit | RFC 5639 |
| `BrainpoolP512r1` | Brainpool 512-bit | RFC 5639 |
The `Curve` trait + per-curve unit structs live in
`ecc::curves`; the LIMBS-generic ECDSA / ECDH internals live in
`ecc::ecdsa` (signing helpers, DER, RFC 6979) and `ecc::curve`
(Jacobian point ops + curve params).
### Edwards / Montgomery curves
| Ed25519 (pure) | `ecc::eddsa` | RFC 8032 §5.1 |
| Ed25519ctx | `ecc::eddsa` | RFC 8032 §5.1.6 |
| Ed25519ph | `ecc::eddsa` | RFC 8032 §5.1.7 |
| X25519 ECDH | `ecc::x25519` | RFC 7748 |
| X448 ECDH | `ecc::x448` | RFC 7748 |
Ed448 is planned but not yet implemented (RFC 8032 Appendix A port
pending).
## Cargo features
```toml
[dependencies]
arcana = { path = "../arcana" } # default = no features
```
| `std` | ☐ | Reserved for future `no_std` work (currently a no-op; the crate already only uses `core` + `alloc`). |
| `rust-crypto-traits` | ☐ | Pulls in `digest 0.10` / `cipher 0.4` / `signature 2.0`. Activates the `bridge` module which wraps every hash in a `digest::Digest` impl for ecosystem interop (HMAC, HKDF, PBKDF2, Argon2, …). |
Default builds are **zero-dependency** (only the workspace-local
`silentops` crate). The `rust-crypto-traits` feature is opt-in for
callers who need to plug arcana hashes into the RustCrypto ecosystem.
## Quick start
### Hashing (SHA-256)
```rust
use arcana::hash::sha256::Sha256;
use arcana::Hasher;
let digest = Sha256::hash(b"hello, arcana");
assert_eq!(digest.len(), 32);
```
### AEAD (AES-128-GCM)
```rust
use arcana::cipher::aes::Aes128;
use arcana::cipher::modes::Gcm;
use arcana::BlockCipher;
let cipher = Aes128::new(&[0x42u8; 16]);
let nonce = [0u8; 12];
let (ct, tag) = Gcm::encrypt(&cipher, &nonce, b"aad", b"plaintext");
let pt = Gcm::decrypt(&cipher, &nonce, b"aad", &ct, &tag).unwrap();
assert_eq!(pt, b"plaintext");
```
### X25519 ECDH
```rust
use arcana::ecc::x25519::{x25519_derive_public, x25519_ecdh};
let alice_sk = [0x77u8; 32];
let bob_sk = [0x88u8; 32];
let alice_pk = x25519_derive_public(&alice_sk);
let bob_pk = x25519_derive_public(&bob_sk);
let shared_a = x25519_ecdh(&alice_sk, &bob_pk);
let shared_b = x25519_ecdh(&bob_sk, &alice_pk);
assert_eq!(shared_a, shared_b);
```
### HMAC-SHA-256 (streaming)
```rust
use arcana::mac::ctx::{Mac, Algorithm};
let mut m = Mac::new(Algorithm::HmacSha256);
m.init(b"secret key").unwrap();
m.update(b"hello, ").unwrap();
m.update(b"world!").unwrap();
let tag = m.sign_to_vec().unwrap();
assert_eq!(tag.len(), 32);
```
### AES-256-CBC (Cipher ctx)
```rust
use arcana::cipher::ctx::{Cipher, Algorithm, Mode, Padding, Direction};
let mut c = Cipher::new(Algorithm::Aes256, Mode::Cbc, Padding::Pkcs7).unwrap();
c.init(Direction::Encrypt, &[0x42u8; 32], &[0x77u8; 16]).unwrap();
let mut out = vec![0u8; 64];
let mut n = c.update(b"hello, streaming!", &mut out).unwrap();
n += c.finalize(&mut out[n..]).unwrap();
out.truncate(n);
// out now contains the padded AES-256-CBC ciphertext.
```
## Typed key wrappers (Zeroize-on-Drop)
Arcana exposes typed key types for RSA, EdDSA, and the seven
short-Weierstrass curves, but **none of them currently implement
`Drop` with `silentops::ct_zeroize`**. Callers must zeroize sensitive
buffers explicitly until the wrappers grow Zeroize-on-Drop — this
gap is tracked under *Known limitations → Side-channel*.
| `ecc::curves` | `PublicKey` | `SecretKey` |
| `ecc::eddsa` | `Ed25519PublicKey` | `Ed25519SecretKey` |
| `rsa::rsa` | `RsaPublicKey` | `RsaSecretKey` (n, d, p, q, dp, dq, qinv as `BigInt`) |
The X25519 / X448 APIs operate on raw `[u8; 32]` / `[u8; 56]` byte
arrays today; a typed wrapper layer is a candidate refresh once the
ECC `SecretKey` wrapper grows `Drop`.
`silentops::ct_zeroize` is available to callers as the canonical
volatile-write zeroizer (it relies on `core::ptr::write_volatile` +
a compiler fence so the compiler cannot elide the writes); apply it
to `bytes` fields of secret-key types before they go out of scope on
the caller side.
## Parameter sets / curve families
### NIST P-curves
| P-256 | `2^256 − 2^224 + 2^192 + 2^96 − 1` | 256 | 32 | 128 |
| P-384 | `2^384 − 2^128 − 2^96 + 2^32 − 1` | 384 | 48 | 192 |
| P-521 | `2^521 − 1` (Mersenne) | 521 | 66 | 256 |
P-521 is the only curve where the SEC1 octet width (66 B) does not
match the internal storage width (`LIMBS * 8 = 72 B`). The
serialization layer strips / left-pads the 6 leading zero bytes at
the boundary.
### Brainpool
| brainpoolP256r1 | 256 bits | 256 | 32 | RFC 5639 |
| brainpoolP384r1 | 384 bits | 384 | 48 | RFC 5639 |
| brainpoolP512r1 | 512 bits | 512 | 64 | RFC 5639 |
### secp256k1
| secp256k1 | `2^256 − 2^32 − 977` | 256 | 32 | SEC 2 §2.4.1 |
`y^2 = x^3 + 7` (a = 0, b = 7). The Bitcoin / Ethereum signing curve.
### Edwards / Montgomery
| Edwards | Ed25519 | 32 (pk), 32 (sk), 64 (sig) | RFC 8032 §5.1 |
| Edwards | Ed448 | *(planned)* | RFC 8032 §5.2 |
| Montgomery | X25519 | 32 (pk = sk = ss) | RFC 7748 |
| Montgomery | X448 | 56 (pk = sk = ss) | RFC 7748 |
### RSA key sizes
The `rsa::rsa::rsa_keygen` constructor accepts arbitrary bit lengths
(within the bounds of the `BigInt` arithmetic). Tested values:
**1024, 2048, 3072, 4096**. Keys above 4096 bits work but key
generation gets slow (BigInt multi-precision is unoptimized). For
typical TLS/CMS usage 2048 or 3072 are the right defaults; 4096 is
documented but expensive.
## Design decisions
1. **Function-oriented API + streaming objects** — one-shot functions
(`Sha256::hash`, `Gcm::encrypt`, `P256::sign_rfc6979`) for the
common case; `Cipher` and `Mac` streaming objects for callers that
feed data in chunks or need caller-provided buffers without heap.
2. **Hash function is always explicit** — ECDSA, RSA-PSS, RSA-PKCS1
all take the hash as a type or enum parameter. No hidden default.
3. **AEAD stays function-oriented** — GCM, CCM, ChaCha20-Poly1305,
XChaCha20-Poly1305 are *not* routed through the streaming
`Cipher` to avoid releasing unverified plaintext during streaming
decryption.
4. **`Curve` trait unifies ECDSA + ECDH + SEC1** — all 7 short-
Weierstrass curves are unit structs implementing one trait, so the
same code works for P-256 and BrainpoolP512r1 with just a type
change.
5. **Three MAC init variants** — `init(key)` for HMAC/CMAC/KMAC,
`init_kmac(key, S)` for KMAC with customization, `init_with_nonce`
for GMAC. Wrong variant → compile-time or runtime error.
6. **Poly1305 excluded from Mac ctx** — it is a one-time MAC; reusing
the key breaks it. Keeping it function-oriented prevents misuse.
7. **CT scalar mul split** — `scalar_mul_point` calls the
ladder-only `point_add_ct` (no branches on point coords); the
variable `point_add` (with `H==0` short-circuits) is reserved for
`double_scalar_mul`, used by ECDSA verify on **public** values.
## Side-channel countermeasures (summary)
### Always-on
These defences are active in every build, regardless of feature
flags:
| Constant-time tag comparison | All AEAD decrypt, MAC verify, ECDSA verify |
| Constant-time field arithmetic | ECC (P-256, P-384, P-521, secp256k1, Brainpool), Ed25519, X25519, X448 |
| CT Montgomery ladder | `ecc::curve::scalar_mul_point` — branch-free across all 7 short-Weierstrass curves |
| `core::hint::black_box` shielding| `field_add` / `field_sub` / `reduce_wide` masks, to keep LLVM from recovering branches |
| No secret-dependent branches | Hash, symmetric, HMAC, CMAC, KMAC, GMAC |
| RFC 6979 deterministic nonce | ECDSA — eliminates nonce-reuse attacks |
| `silentops::ct_zeroize` available| Caller can zeroize buffers holding secrets explicitly |
### Feature-gated
Arcana does **not** ship a feature-gated SCA layer today. Algorithm
choice is the only knob: prefer the curve / cipher with the strongest
intrinsic CT properties for your threat model (e.g. Curve25519 over
NIST P-curves on cache-shared targets, ChaCha20-Poly1305 over AES-GCM
on cores without AES-NI).
A `sca-protected` feature mirroring the quantica side (masking +
shuffled NTT) is on the roadmap for symmetric primitives whose state
is amenable; classical curves are protected algorithmically (CT
ladder) rather than by masking.
### Timing leakage verification (dudect)
The shared `silentops::verify` module implements the dudect
methodology of Reparaz, Balasch & Verbauwhede (2017). Arcana does
not yet ship a pre-built dudect harness of its own; instructions for
hooking new test scenarios into the workspace harness live in
`silentops/examples/ct_verify_pqc.rs` (the post-quantum harness) —
mirror that file with arcana primitives when running a CT campaign.
The eventual third-party evaluation pass will require dudect runs on:
* `scalar_mul_point` (the Montgomery ladder) — already audited at
release-asm level: 0 secret-dependent branches in the loop body.
* `field_inv` / `scalar_inv` (Fermat's little theorem ladder).
* RSA CRT decrypt path (`rsa::rsa::rsa_decrypt_raw`).
* AEAD decrypt tag compare (constant-time via `silentops::ct_eq`).
A `t`-statistic with `|t| < 4.5` after ~10⁶ samples is considered
passing (`p < 10⁻⁵`).
### Known residual surface
The following attack surfaces are *not* defended against and are
documented here so the reader knows what they are deploying:
| AES S-box | Table-based lookup — cache-line leak | Bitsliced / AES-NI backend (not yet shipped) |
| DES / 3DES S-boxes | Table-based | Legacy only; avoid on SCA-sensitive targets |
| RSA CRT decrypt | `BigInt` ops not formally CT-audited | Audit + dudect run scheduled |
| Heap allocations | Secret-key buffers come from `alloc` | Caller-provided fixed buffers (planned refactor) |
| Zeroize-on-Drop | Typed key wrappers do not yet `Drop` | Wrap secret types with `silentops::ct_zeroize` |
| DPA / EM / fault | Out of scope for software library | See workspace-level SCA design docs |
| Compiler CMOV | Bit-mask CT depends on `black_box` | `silentops` asm backends on x86_64 / aarch64 |
### Per-algorithm deep dives
The summary above lists which countermeasures are active; the full
per-algorithm SCA analyses — threat matrices, attack references, code
pointers, residual risks — live under
`arcana/doc/sca/countermeasures/` in the repository. The Sphinx
documentation pack (`./gendoc.sh arcana`) inlines them as a
navigable cross-linked tree below.
## Performance
Arcana does not yet ship a dedicated benchmarking crate (no
`arcana_bench` companion exists at workspace level). Representative
single-threaded numbers obtained from `cargo test --release` timings
on x86_64 desktop hardware:
| AES-128-GCM (1 KiB)| — | < 0.01 ms | < 0.01 ms |
| ChaCha20-Poly1305 | — | < 0.01 ms | < 0.01 ms |
| SHA-256 (1 KiB) | — | < 0.01 ms | — |
| RSA-2048 (PKCS#1) | ~150 ms | ~6 ms | < 0.5 ms |
| ECDSA P-256 | ~1 ms | ~3 ms | ~3 ms |
| Ed25519 | < 0.1 ms | < 0.1 ms | < 0.5 ms |
| X25519 | — | < 0.1 ms | < 0.1 ms |
Numbers are coarse and should not be cited; they vary widely with
hardware and feature flags. A proper Criterion-based bench harness
mirroring `quantica_bench` is on the roadmap.
## Building
### Desktop / server (default)
```bash
# Build everything (opt-level=2, CT-safe)
cargo build --release -p arcana
# Build with the RustCrypto trait bridge feature
cargo build --release -p arcana --features rust-crypto-traits
# Run all tests
cargo test --release -p arcana
# Generate the rustdoc API reference (strict mode: -D warnings -D missing-docs)
RUSTDOCFLAGS="-D warnings -D missing-docs" cargo doc -p arcana --no-deps
```
Both the default and the `rust-crypto-traits` configurations pass
strict-doc mode with zero warnings.
### `no_std` / bare-metal cross-compile
The crate is still in transition: it currently uses `Vec<u8>` /
`alloc` end-to-end and the `std` feature is reserved as a no-op for
future use. Bare-metal cross-compilation will work once the
caller-provided-buffer refactor lands; today it is only verified at
the workspace level for `quantica`. Targeted rustc targets:
```bash
# Install the targets we care about
rustup target add thumbv6m-none-eabi # Cortex-M0/M0+
rustup target add thumbv7em-none-eabihf # Cortex-M4/M7
rustup target add thumbv8m.main-none-eabihf # Cortex-M33 (TrustZone)
rustup target add riscv32imc-unknown-none-elf # ESP32-C3, SiFive
```
### Cargo profiles
The workspace `Cargo.toml` declares three profiles:
| `release` | 2 | Yes (Rust source-level + `black_box`) | Desktop / server production |
| `release-embedded` | z + abort | Yes (asm CT backends from `silentops`) | Embedded, minimum size |
| `release-bench` | 3 | **No** (LLVM may break CT patterns) | Benchmarks only |
> ⚠️ `opt-level=3` can defeat constant-time guarantees: LLVM may
> convert bitwise mask patterns into conditional memory accesses.
> Always use `opt-level=2` or lower for security-critical builds, or
> rely on the assembly CT backends from `silentops` (`asm-aarch64`,
> `asm-thumbv7`, `asm-thumbv6m`, `asm-riscv32`) which bypass the
> compiler entirely.
## Test validation
All implementations are validated against four independent vector
suites; total ≈ 351 tests in the default build (358 with
`rust-crypto-traits`).
### NIST CAVP / FIPS / RFC happy-path conformance
The bulk of the conformance evidence comes from RFC and FIPS pinned
vectors (in-source tests, `arcana/src/**/tests`) plus the official
NIST CAVP `.rsp` corpus mirrored under `arcana/tests/cavp/`:
| SHA-1/2 family | ~25 | FIPS 180-4 examples, NIST CAVP, RFC 6234 |
| SHA3 / SHAKE / cSHAKE | ~25 | FIPS 202 examples, NIST SP 800-185 sample 1/2/4 |
| BLAKE2 | ~10 | RFC 7693 §B test vectors |
| AES (all modes) | ~40 | FIPS 197, NIST SP 800-38A/B/C/D, IEEE 1619, RFC 4493 |
| ChaCha20-Poly1305 | ~15 | RFC 8439 §2.8 + draft-irtf-cfrg-xchacha §3 |
| RSA | ~20 | PKCS#1 / OAEP / PSS round-trips, RFC 8017 §A.1 |
| ECDSA | ~50 | RFC 6979 §A.2, NIST P-256/384/521 generators, 7 curves |
| EdDSA | ~25 | RFC 8032 §7.1 (Ed25519 pure + ctx + ph) |
| X25519 / X448 | ~20 | RFC 7748 §6 |
| HMAC | ~15 | RFC 4231, RFC 2202 |
| CMAC / KMAC / GMAC | ~20 | RFC 4493, NIST SP 800-185, NIST SP 800-38D |
| Streaming Cipher / Mac ctx | ~40 | Round-trips × (mode × padding), error paths |
| DER / PEM key serialization | ~10 | PKCS#1, SEC1, SPKI, PKCS#8 round-trips |
| **CAVP corpus** | ~2 200 | NIST CAVP `.rsp` (SHA, AES, HMAC, ECDSA SigVer) |
| **ACVP corpus** | ~1 250 | NIST ACVP JSON (SHA3, AES-CTR/CCM/XTS, HMAC-SHA3, ECDSA SigVer) |
### Wycheproof
Vectors from the [C2SP/wycheproof](https://github.com/C2SP/wycheproof)
project, covering malformed inputs, corrupted keys, oversized DER
INTEGERs, edge-case ECDSA signatures, and other negative tests the
NIST happy-path vectors do not exercise. Each vector carries a
`result` field — `valid`, `invalid`, or `acceptable` — against which
the implementation's accept / reject decision is compared.
| AES-GCM | ~250 | invalid IVs, tag truncation, corrupted ciphertext |
| AES-CBC-PKCS5 | ~180 | padding-oracle robustness |
| ChaCha20-Poly1305 | ~120 | RFC 8439 negatives |
| ECDSA P-256 | ~380 | edge cases on r/s, oversized DER (also closed a real bug) |
| ECDSA P-384 | ~270 | same edge-case set on the larger curve |
| EdDSA Ed25519 | ~160 | RFC 8032 §5.1 corner cases |
| RSA OAEP / PSS | ~240 | wrong hash, modulus length, label |
| HMAC-SHA-2 | ~70 | wrong key length, truncated tags |
| **Total** | **~1 670** | |
The Wycheproof import surfaced (and we fixed) one ECDSA
oversized-`s` bug — see commit `191d40e`.
### Custom negative / robustness tests
Hand-curated tests targeting the specific error paths of each typed
key wrapper — wrong-length inputs, off-curve public keys (defence
against the invalid-curve attack), tampered signatures, infinity
encodings, malformed DER, etc. Around 30 tests across the families.
### Running everything
```bash
cargo test --release -p arcana
cargo test --release -p arcana --features rust-crypto-traits
```
### Policy on test suites
> A necessary condition for adding a new cryptographic primitive
> to this crate is the availability of a public reference test
> suite for it. When a new peer-reviewed test corpus appears —
> a refreshed Wycheproof release, a new CAVP tranche, an IETF
> CFRG vector set — we import it and extend the test matrix
> accordingly, and call the refresh out in the changelog.
## Examples
### Rust
```bash
cargo run -p arcana --release --example hash_demo # SHA-256, SHA3, SHAKE
cargo run -p arcana --release --example aes_demo # AES-128-GCM encrypt/decrypt
cargo run -p arcana --release --example rsa_demo # RSA-2048 sign + OAEP encrypt
cargo run -p arcana --release --example ecdsa_demo # ECDSA P-256 keygen/sign/verify
cargo run -p arcana --release --example eddsa_demo # Ed25519 sign/verify
cargo run -p arcana --release --example x25519_demo # X25519 ECDH key exchange
```
### C FFI
For C consumers, the `arcana_ffi` companion crate exposes ~20
`extern "C"` functions and ships a standalone `test_arcana.c`
example program. Build the shared library and run the C test:
```bash
cargo build --release -p arcana_ffi
gcc -O2 -o test_arcana arcana_ffi/examples/test_arcana.c \
-Iarcana_ffi/include -Ltarget/release -larcana_ffi -lpthread -ldl -lm
LD_LIBRARY_PATH=target/release ./test_arcana
```
The generated C header (`arcana.h`) is kept under the FFI crate's
`include/` directory.
## Module map
```text
arcana/src/
lib.rs Crate root: Hasher, Xof, BlockCipher traits
hash/
mod.rs Re-exports
sha1.rs SHA-1 (FIPS 180-4) — legacy
sha224.rs SHA-224
sha256.rs SHA-256
sha384.rs SHA-384
sha512.rs SHA-512
sha512_trunc.rs SHA-512/224, SHA-512/256
sha3.rs SHA3-224/256/384/512, SHAKE128/256, cSHAKE128/256
blake2.rs BLAKE2b, BLAKE2s (RFC 7693)
ripemd160.rs RIPEMD-160 (ISO 10118-3) — legacy
cipher/
mod.rs Re-exports
aes.rs AES-128/192/256 (FIPS 197, table-based S-box)
des.rs DES + Triple-DES (FIPS 46-3)
modes.rs ECB, CBC, CTR, GCM (SP 800-38A/D)
ccm.rs AES-CCM AEAD (SP 800-38C, RFC 3610)
xts.rs AES-XTS disk encryption (IEEE 1619)
chacha20.rs ChaCha20 stream cipher (RFC 8439)
poly1305.rs Poly1305 one-time MAC (RFC 8439)
chacha20poly1305.rs ChaCha20-Poly1305 AEAD (RFC 8439)
xchacha20poly1305.rs XChaCha20-Poly1305 AEAD (24-byte nonce)
ctx.rs Streaming Cipher: init/update/finalize + padding
mac/
mod.rs Re-exports
ctx.rs Streaming Mac: HMAC×8, CMAC×4, KMAC×2, GMAC×3
rsa/
mod.rs Re-exports
bigint.rs Arbitrary-precision integer arithmetic
rsa.rs Key generation, raw encrypt/decrypt (CRT)
pkcs1.rs PKCS#1 v1.5 enc + sig (8 hash functions)
oaep.rs RSAES-OAEP (RFC 8017 §7.1)
pss.rs RSASSA-PSS (RFC 8017 §8.1)
ecc/
mod.rs Re-exports
field.rs Multi-precision field arithmetic (P-256..P-521, Curve25519, Curve448)
curve.rs Short-Weierstrass curve params + Jacobian point ops + CT scalar_mul_point
curves.rs Curve trait + 7 unit structs + dispatch macro (shared ECDSA/ECDH)
ecdsa.rs ECDSA Signature + DER + RFC 6979 + LIMBS-generic internals
ecdh.rs ECDH integration tests (impl lives in ecdsa.rs)
eddsa.rs Ed25519 pure + ctx + ph (RFC 8032)
x25519.rs X25519 ECDH (RFC 7748)
x448.rs X448 ECDH (RFC 7748)
encoding/
mod.rs Re-exports
der.rs DER encoder / parser (canonical, strict)
pem.rs PEM armor / dearmor
keys.rs PKCS#1 / SEC1 / SPKI / PKCS#8 key serialization
bridge/
mod.rs RustCrypto digest::Digest adapters (feature-gated)
```
## Known limitations
### Side-channel protection
* AES uses a table-based S-box — vulnerable to cache-timing on
shared L1 targets. Bitsliced / AES-NI backend is not yet shipped.
* DES / 3DES use table-based S-boxes; legacy use only.
* `RsaSecretKey` / `Ed25519SecretKey` / `SecretKey` (ECC) lack
Zeroize-on-Drop. Callers must zeroize sensitive buffers explicitly
via `silentops::ct_zeroize`.
* Heap allocations on the secret path — secret-key buffers come from
`alloc` rather than caller-provided fixed buffers. A future
refactor will thread `&mut [u8]` end-to-end for bare-metal
stack-only operation.
* RSA CRT decrypt path (`rsa::rsa::rsa_decrypt_raw`) has not been
formally CT-audited; a `BigInt` review + dudect run is scheduled.
* Bit-mask CT primitives are defended against LLVM
branch-recovery via `core::hint::black_box`; on targets without
the `silentops` asm backend (e.g. WebAssembly) the CT guarantee is
best-effort source-level only.
### Standards conformance
* **Ed448** is not yet implemented (RFC 8032 Appendix A port pending —
prior WebFetch attempts to fetch the RFC reference Python were
inconsistent).
* **AES-OCB** (RFC 7253) is not implemented; will be added on
demand.
* **DER/PEM key serialization** covers PKCS#1, SEC1, SPKI, PKCS#8;
ASN.1 BER (non-canonical) input is rejected by design.
### Portability
* The crate depends on `alloc` (`Vec<u8>` everywhere); a true
`no_std` mode requires the caller-provided-buffer refactor noted
above.
* No platform-specific OsRng adapter; ECDSA and RSA keygen take a
user-supplied RNG callback. The caller is responsible for wiring a
hardware-RNG / `BCryptGenRandom` / `SecRandomCopyBytes` /
`/dev/urandom` source on their target.
### Testing
* No fuzz harness yet (`cargo-fuzz` is on the roadmap).
* No CI/CD pipeline yet — the workspace `.gitea/workflows/` will
cover the test matrix once the post-quantum side closes its CT3
(QEMU-user) milestone.
* No formal third-party evaluation yet; arcana feeds the same
documentation pack as quantica and is aimed at the same evaluation
target.
## Roadmap
The full hardening roadmap lives under `arcana/doc/sca/` (HTML
rendered by `./gendoc.sh arcana`). The summary below is the project's
**living plan towards a third-party evaluation**, indexed by Tier
item identifier so each row maps to a stable cross-reference in
the source code, the SCA annex and the workspace `SECURITY.md`
lifecycle.
Status legend: ✅ done · 🔧 in progress · 📋 planned · 💤 deferred.
### Tier 1 — Active vulnerabilities (critical path)
| T1-A | Fixsliced AES (Adomnicai-Peyrin TCHES 2021/1) — replace table-based S-box | 📋 |
| T1-B | Minerva audit on `bits2int` / `reduce_mod_n` / `scalar_inv` / `sample_random_scalar` | 📋 |
| T1-C | Aumüller RSA-CRT against Bellcore (formally verified by Rauzy-Guilley) | 📋 |
| T1-D | Hedged ECDSA / EdDSA mode (CFRG `det-sigs-with-noise`) | 📋 |
| T1-E | RSA bigint CT audit (Montgomery_mul, cmp, pow_mod, mod_inv) | 📋 |
| T1-F | Ed25519 scalar-mul audit (mirror of `76191c1`) | 📋 |
| T1-G | X25519 / X448 ladder audit + `black_box` shielding | 📋 |
### Tier 2 — Hardening for evaluation
| T2-A | Z-coordinate randomization (Brier-Joye 2002) on ECC | 📋 |
| T2-B | Scalar blinding (Coron 1999) on ECC | 📋 |
| T2-D | First-order Boolean masking of SHA-2 (Belenky TCHES 2023/3 — CDPA) | 📋 |
| T2-E | Zeroize-on-Drop on `RsaSecretKey`, `Ed25519SecretKey`, ECC `SecretKey` | 📋 |
| T2-G | First-order Boolean masking on fixsliced AES (post T1-A) | 📋 |
| T2-H | CT carry-less GHASH multiplier (PCLMULQDQ / PMULL on host; bitsliced on embedded) | 📋 |
| T2-I | RSA message blinding + exponent blinding (Coron 1999) | 📋 |
| T2-J | PKCS#1 v1.5 CT padding-oracle handling (RFC 8017 §7.2.2) | 📋 |
| T2-K | X25519 / X448 small-subgroup contributory check audit | 📋 |
### Tier 3 — Verification tooling
The cross-arch test infrastructure tracked under quantica's T3-A
/ T3-B (qemu-user matrix on the three non-x86_64 Linux triplets,
qemu-system matrix on the bare-metal targets, and the
semihosting host↔guest vector-streaming protocol — see
`.forgejo/workflows/qemu-cross-tests.yml`, `Cross.toml`, and the
`tools/qemu-*.sh` drivers) is workspace-wide and already exercises
arcana — `cross test --target aarch64-unknown-linux-gnu -p arcana`
runs the full 315-vector arcana KAT set under qemu-user with the
`asm-aarch64` `silentops` backend on every PR. arcana's own Tier 3
rows below cover the additional CT-evidence harnesses (ctgrind /
dudect) that are not subsumed by the cross-arch matrix.
| T3-A | ctgrind harness (mirror of quantica's, via `silentops::ct_grind`) | 📋 |
| T3-B | dudect harness on `scalar_mul_point`, `rsa_decrypt`, AEAD decrypt, fixsliced AES | 📋 |
### Tier 4 — Deferred / beyond the current evaluation scope
| T4-RSA-A | Joye-Tunstall infective computation (multi-fault resistance) | 💤 |
| T4-AES-A | AES last-round redundancy + infective DFA defence | 💤 |
| T4-CC | Higher-order DPA / template (2-share masking) for CC EAL4+ | 💤 |
### Tier 5 — Documentation pass
Cross-cutting documentation work, orthogonal to the cryptographic
tiers above. Planned (not deferred); timing to be sequenced
against the external evaluation calendar. Items are workspace-wide
and shared with the `quantica` Tier 5.
| T5-A | Workspace-wide doc pass (`quantica` + `arcana`): neutralise evaluation-target references — replace any CSPN-/ANSSI-specific language with generic *evaluation / certification / audit* terminology so the doc set reads cleanly against any third-party reviewer | ✅ |
| T5-B | TOC review across the workspace doc set (`doc/TOC.md` contract + per-crate `doc/` trees) — reorder chapters into 4 thematic clusters; rename ch.8 "Side-channel countermeasures" → "(summary)" + add `Per-algorithm deep dives` H3 bridging to the Sphinx pack | ✅ |
### ECC follow-ups (already shipped)
| — | `ecc::curves` split (Curve trait + unit structs out of `ecdsa.rs`) | ✅ commit `0feb5b5` |
| — | CT hardening of `scalar_mul_point` (Montgomery ladder, branchless `point_add_ct`, `core::hint::black_box` shielding on `field_*` masks) | ✅ commit `76191c1` |
| Ed448 | RFC 8032 Appendix A port | 💤 (RFC fetch fragile, deferred until reference Python is available locally) |
| AES-OCB | RFC 7253 | 💤 (skip unless explicit ask) |
### Suggested execution order (critical path)
1. **Sprint 1**: T1-A + T1-C + T1-E — closes the active attack surface.
2. **Sprint 2**: T2-A + T2-B + T2-E + T1-B Minerva audit — hardens ECC to SOTA.
3. **Sprint 3**: T3-A ctgrind + T3-B dudect — provides CT evidence for the eval.
4. **Sprint 4**: T1-D hedged + T2-D HMAC masking — final hardening.
5. Evaluation doc pack ships, referencing each countermeasure to its paper.
Effort estimate: 4 – 6 weeks full-time for T1 + T2 + T3, plus ~2 weeks
documentation. Updates to this table are tracked in the change log
of `arcana/doc/sca/index.rst`.
## References
- [FIPS 180-4](https://csrc.nist.gov/publications/detail/fips/180/4/final) — SHA-1, SHA-2 family
- [FIPS 197](https://csrc.nist.gov/publications/detail/fips/197/final) — AES
- [FIPS 202](https://csrc.nist.gov/publications/detail/fips/202/final) — SHA-3, SHAKE
- [FIPS 186-5](https://csrc.nist.gov/publications/detail/fips/186/5/final) — ECDSA
- [NIST SP 800-185](https://csrc.nist.gov/publications/detail/sp/800-185/final) — KMAC, cSHAKE
- [RFC 7693](https://www.rfc-editor.org/rfc/rfc7693) — BLAKE2
- [RFC 8017](https://www.rfc-editor.org/rfc/rfc8017) — PKCS#1 (RSA)
- [RFC 8032](https://www.rfc-editor.org/rfc/rfc8032) — EdDSA (Ed25519, Ed448)
- [RFC 7748](https://www.rfc-editor.org/rfc/rfc7748) — X25519, X448
- [RFC 8439](https://www.rfc-editor.org/rfc/rfc8439) — ChaCha20-Poly1305
- [RFC 6979](https://www.rfc-editor.org/rfc/rfc6979) — Deterministic ECDSA
- [RFC 4493](https://www.rfc-editor.org/rfc/rfc4493) — AES-CMAC
- [RFC 5639](https://www.rfc-editor.org/rfc/rfc5639) — Brainpool curves
- [SEC 2 v2.0](https://www.secg.org/sec2-v2.pdf) — secp256k1 / NIST P-256 / NIST P-384
- [C2SP / Wycheproof](https://github.com/C2SP/wycheproof) — edge-case and negative test vectors
- [NIST CAVP](https://csrc.nist.gov/projects/cryptographic-algorithm-validation-program) — official conformance test vectors
- [NIST ACVP-Server](https://github.com/usnistgov/ACVP-Server) — modern conformance test vectors
- Reparaz, Balasch & Verbauwhede (2017) — *"dude, is my code constant time?"*
(the dudect methodology used in `silentops::verify`)
## License
Apache-2.0.