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 workspace.
Design rules
The crate inherits the krypteia workspace design rules:
Pure Rust, zero external crates — only
core(andalloc);stdis optional behind a feature flag. The only workspace dependency issilentopsfor shared CT primitives and timing-leak verification.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_stdis on the roadmap.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.
Validated — every algorithm is tested against pinned RFC / NIST / FIPS reference vectors, plus three external corpora (Wycheproof, NIST CAVP, NIST ACVP).
C FFI-exposable — the companion crate
arcana_ffiexports ~20extern "C"functions with a C header atarcana_ffi/include/arcana.h.
Algorithms
Arcana exposes six primitive families, all in src/:
Hash functions
Algorithm |
Output |
Module |
Standard |
|---|---|---|---|
SHA-1 |
160 b |
|
FIPS 180-4 |
SHA-224 |
224 b |
|
FIPS 180-4 |
SHA-256 |
256 b |
|
FIPS 180-4 |
SHA-384 |
384 b |
|
FIPS 180-4 |
SHA-512 |
512 b |
|
FIPS 180-4 |
SHA-512/224 |
224 b |
|
FIPS 180-4 |
SHA-512/256 |
256 b |
|
FIPS 180-4 |
SHA3-224 |
224 b |
|
FIPS 202 |
SHA3-256 |
256 b |
|
FIPS 202 |
SHA3-384 |
384 b |
|
FIPS 202 |
SHA3-512 |
512 b |
|
FIPS 202 |
SHAKE128 |
XOF |
|
FIPS 202 |
SHAKE256 |
XOF |
|
FIPS 202 |
cSHAKE128 |
XOF |
|
NIST SP 800-185 |
cSHAKE256 |
XOF |
|
NIST SP 800-185 |
BLAKE2b |
1-512 b |
|
RFC 7693 |
BLAKE2s |
1-256 b |
|
RFC 7693 |
RIPEMD-160 |
160 b |
|
ISO/IEC 10118-3 |
Symmetric ciphers and modes
Algorithm |
Module |
Standard |
|---|---|---|
AES-128 / 192 / 256 |
|
FIPS 197 |
DES, Triple-DES (EDE) |
|
FIPS 46-3 |
ECB / CBC / CTR / GCM |
|
NIST SP 800-38A/D |
AES-CCM AEAD |
|
NIST SP 800-38C, RFC 3610 |
AES-XTS disk encryption |
|
IEEE 1619 |
ChaCha20 stream cipher |
|
RFC 8439 |
Poly1305 one-time MAC |
|
RFC 8439 |
ChaCha20-Poly1305 AEAD |
|
RFC 8439 |
XChaCha20-Poly1305 AEAD (24B nonce) |
|
draft-irtf-cfrg-xchacha |
Streaming 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)
Family |
Algorithms |
Standard |
|---|---|---|
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
Scheme |
Module |
Standard |
|---|---|---|
PKCS#1 v1.5 enc+sig |
|
RFC 8017 §7-8 |
OAEP encryption |
|
RFC 8017 §7.1 |
RSASSA-PSS signature |
|
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):
Wrapper |
Curve |
Standard |
|---|---|---|
|
NIST P-256 |
FIPS 186-5 |
|
NIST P-384 |
FIPS 186-5 |
|
NIST P-521 |
FIPS 186-5 |
|
secp256k1 |
SEC 2 §2.4.1 |
|
Brainpool 256-bit |
RFC 5639 |
|
Brainpool 384-bit |
RFC 5639 |
|
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
Algorithm |
Module |
Standard |
|---|---|---|
Ed25519 (pure) |
|
RFC 8032 §5.1 |
Ed25519ctx |
|
RFC 8032 §5.1.6 |
Ed25519ph |
|
RFC 8032 §5.1.7 |
X25519 ECDH |
|
RFC 7748 |
X448 ECDH |
|
RFC 7748 |
Ed448 is planned but not yet implemented (RFC 8032 Appendix A port pending).
Cargo features
[dependencies]
arcana = { path = "../arcana" } # default = no features
Feature |
Default |
Effect |
|---|---|---|
|
☐ |
Reserved for future |
|
☐ |
Pulls in |
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)
use arcana::hash::sha256::Sha256;
use arcana::Hasher;
let digest = Sha256::hash(b"hello, arcana");
assert_eq!(digest.len(), 32);
AEAD (AES-128-GCM)
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
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)
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)
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.
Module |
Public |
Secret (currently NOT zeroized on drop) |
|---|---|---|
|
|
|
|
|
|
|
|
|
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
Curve |
Field prime |
Order bit length |
felem (B) |
sec. level |
|---|---|---|---|---|
P-256 |
|
256 |
32 |
128 |
P-384 |
|
384 |
48 |
192 |
P-521 |
|
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
Curve |
Field prime size |
Order bit length |
felem (B) |
Source |
|---|---|---|---|---|
brainpoolP256r1 |
256 bits |
256 |
32 |
RFC 5639 |
brainpoolP384r1 |
384 bits |
384 |
48 |
RFC 5639 |
brainpoolP512r1 |
512 bits |
512 |
64 |
RFC 5639 |
secp256k1
Curve |
Field prime |
Order bit length |
felem (B) |
Source |
|---|---|---|---|---|
secp256k1 |
|
256 |
32 |
SEC 2 §2.4.1 |
y^2 = x^3 + 7 (a = 0, b = 7). The Bitcoin / Ethereum signing curve.
Edwards / Montgomery
Family |
Curve |
Wire size (B) |
Source |
|---|---|---|---|
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
Function-oriented API + streaming objects — one-shot functions (
Sha256::hash,Gcm::encrypt,P256::sign_rfc6979) for the common case;CipherandMacstreaming objects for callers that feed data in chunks or need caller-provided buffers without heap.Hash function is always explicit — ECDSA, RSA-PSS, RSA-PKCS1 all take the hash as a type or enum parameter. No hidden default.
AEAD stays function-oriented — GCM, CCM, ChaCha20-Poly1305, XChaCha20-Poly1305 are not routed through the streaming
Cipherto avoid releasing unverified plaintext during streaming decryption.Curvetrait 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.Three MAC init variants —
init(key)for HMAC/CMAC/KMAC,init_kmac(key, S)for KMAC with customization,init_with_noncefor GMAC. Wrong variant → compile-time or runtime error.Poly1305 excluded from Mac ctx — it is a one-time MAC; reusing the key breaks it. Keeping it function-oriented prevents misuse.
CT scalar mul split —
scalar_mul_pointcalls the ladder-onlypoint_add_ct(no branches on point coords); the variablepoint_add(withH==0short-circuits) is reserved fordouble_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:
Defence |
Scope |
|---|---|
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 |
|
|
|
No secret-dependent branches |
Hash, symmetric, HMAC, CMAC, KMAC, GMAC |
RFC 6979 deterministic nonce |
ECDSA — eliminates nonce-reuse attacks |
|
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:
Primitive |
Issue |
Mitigation path |
|---|---|---|
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 |
|
Audit + dudect run scheduled |
Heap allocations |
Secret-key buffers come from |
Caller-provided fixed buffers (planned refactor) |
Zeroize-on-Drop |
Typed key wrappers do not yet |
Wrap secret types with |
DPA / EM / fault |
Out of scope for software library |
See workspace-level SCA design docs |
Compiler CMOV |
Bit-mask CT depends on |
|
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:
Algorithm |
KeyGen / setup |
Sign / Encrypt |
Verify / Decrypt |
|---|---|---|---|
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)
# 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:
# 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:
Profile |
opt-level |
CT guarantee |
Use case |
|---|---|---|---|
|
2 |
Yes (Rust source-level + |
Desktop / server production |
|
z + abort |
Yes (asm CT backends from |
Embedded, minimum size |
|
3 |
No (LLVM may break CT patterns) |
Benchmarks only |
⚠️
opt-level=3can defeat constant-time guarantees: LLVM may convert bitwise mask patterns into conditional memory accesses. Always useopt-level=2or lower for security-critical builds, or rely on the assembly CT backends fromsilentops(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/:
Family |
# tests |
Vector sources |
|---|---|---|
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 |
ACVP corpus |
~1 250 |
NIST ACVP JSON (SHA3, AES-CTR/CCM/XTS, HMAC-SHA3, ECDSA SigVer) |
Wycheproof
Vectors from the 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.
Family |
Vectors |
Notes |
|---|---|---|
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
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
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:
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
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 viasilentops::ct_zeroize.Heap allocations on the secret path — secret-key buffers come from
allocrather 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; aBigIntreview + dudect run is scheduled.Bit-mask CT primitives are defended against LLVM branch-recovery via
core::hint::black_box; on targets without thesilentopsasm 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 trueno_stdmode 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/urandomsource on their target.
Testing
No fuzz harness yet (
cargo-fuzzis 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)
Id |
Item |
Status |
|---|---|---|
T1-A |
Fixsliced AES (Adomnicai-Peyrin TCHES 2021/1) — replace table-based S-box |
📋 |
T1-B |
Minerva audit on |
📋 |
T1-C |
Aumüller RSA-CRT against Bellcore (formally verified by Rauzy-Guilley) |
📋 |
T1-D |
Hedged ECDSA / EdDSA mode (CFRG |
📋 |
T1-E |
RSA bigint CT audit (Montgomery_mul, cmp, pow_mod, mod_inv) |
📋 |
T1-F |
Ed25519 scalar-mul audit (mirror of |
📋 |
T1-G |
X25519 / X448 ladder audit + |
📋 |
Tier 2 — Hardening for evaluation
Id |
Item |
Status |
|---|---|---|
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 |
📋 |
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.
Id |
Item |
Status |
|---|---|---|
T3-A |
ctgrind harness (mirror of quantica’s, via |
📋 |
T3-B |
dudect harness on |
📋 |
Tier 4 — Deferred / beyond the current evaluation scope
Id |
Item |
Status |
|---|---|---|
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.
Id |
Item |
Status |
|---|---|---|
T5-A |
Workspace-wide doc pass ( |
✅ |
T5-B |
TOC review across the workspace doc set ( |
✅ |
ECC follow-ups (already shipped)
Id |
Item |
Status |
|---|---|---|
— |
|
✅ commit |
— |
CT hardening of |
✅ commit |
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)
Sprint 1: T1-A + T1-C + T1-E — closes the active attack surface.
Sprint 2: T2-A + T2-B + T2-E + T1-B Minerva audit — hardens ECC to SOTA.
Sprint 3: T3-A ctgrind + T3-B dudect — provides CT evidence for the eval.
Sprint 4: T1-D hedged + T2-D HMAC masking — final hardening.
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 — SHA-1, SHA-2 family
FIPS 197 — AES
FIPS 202 — SHA-3, SHAKE
FIPS 186-5 — ECDSA
NIST SP 800-185 — KMAC, cSHAKE
RFC 7693 — BLAKE2
RFC 8017 — PKCS#1 (RSA)
RFC 8032 — EdDSA (Ed25519, Ed448)
RFC 7748 — X25519, X448
RFC 8439 — ChaCha20-Poly1305
RFC 6979 — Deterministic ECDSA
RFC 4493 — AES-CMAC
RFC 5639 — Brainpool curves
SEC 2 v2.0 — secp256k1 / NIST P-256 / NIST P-384
C2SP / Wycheproof — edge-case and negative test vectors
NIST CAVP — official conformance test vectors
NIST 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.