arcana/lib.rs
1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 Cédric Mesnil <cslashm@pm.me>
3
4//! # arcana — classical cryptography for the `krypteia` workspace
5//!
6//! Pure-Rust implementations of widely-used classical primitives —
7//! hash functions, symmetric ciphers, AEAD modes, RSA, ECDSA / ECDH,
8//! EdDSA, X25519, and X448 — sharing the same side-channel
9//! countermeasure toolkit ([`silentops`](https://docs.rs/silentops))
10//! used by the post-quantum side of the workspace
11//! ([`quantica`](https://docs.rs/quantica)).
12//!
13//! Zero external runtime dependencies (only `std` / `alloc` and the
14//! workspace-local `silentops`); constant-time on the data path.
15//!
16//! # Algorithms
17//!
18//! ## Hash functions ([`hash`])
19//!
20//! | Algorithm | Output | Module |
21//! |--------------|---------|-------------------------------------------------|
22//! | SHA-1 | 160 b | [`hash::sha1::Sha1`] (legacy) |
23//! | SHA-224 | 224 b | [`hash::sha224::Sha224`] |
24//! | SHA-256 | 256 b | [`hash::sha256::Sha256`] |
25//! | SHA-384 | 384 b | [`hash::sha384::Sha384`] |
26//! | SHA-512 | 512 b | [`hash::sha512::Sha512`] |
27//! | SHA-512/224 | 224 b | [`hash::sha512_trunc::Sha512_224`] |
28//! | SHA-512/256 | 256 b | [`hash::sha512_trunc::Sha512_256`] |
29//! | SHA3-224 | 224 b | [`hash::sha3::Sha3_224`] |
30//! | SHA3-256 | 256 b | [`hash::sha3::Sha3_256`] |
31//! | SHA3-384 | 384 b | [`hash::sha3::Sha3_384`] |
32//! | SHA3-512 | 512 b | [`hash::sha3::Sha3_512`] |
33//! | SHAKE128 | XOF | [`hash::sha3::Shake128`] |
34//! | SHAKE256 | XOF | [`hash::sha3::Shake256`] |
35//! | cSHAKE128 | XOF | [`hash::sha3::CShake128`] |
36//! | cSHAKE256 | XOF | [`hash::sha3::CShake256`] |
37//! | BLAKE2b | 1-512 b | [`hash::blake2::Blake2b`] |
38//! | BLAKE2s | 1-256 b | [`hash::blake2::Blake2s`] |
39//! | RIPEMD-160 | 160 b | [`hash::ripemd160::Ripemd160`] (legacy) |
40//!
41//! ## Symmetric ciphers and modes ([`cipher`])
42//!
43//! | Algorithm | Module |
44//! |-----------------------------------|----------------------------------------------|
45//! | AES-128 / 192 / 256 | [`cipher::aes`] |
46//! | DES, Triple-DES (EDE) | [`cipher::des`] |
47//! | ECB, CBC, CTR, GCM modes | [`cipher::modes`] |
48//! | **AES-CCM** (RFC 3610) AEAD | [`cipher::ccm`] |
49//! | **AES-XTS** (IEEE 1619) disk crypto| [`cipher::xts`] |
50//! | **ChaCha20** (RFC 8439) | [`cipher::chacha20`] |
51//! | **Poly1305** one-time MAC | [`cipher::poly1305`] |
52//! | **ChaCha20-Poly1305** AEAD | [`cipher::chacha20poly1305`] |
53//! | **XChaCha20-Poly1305** AEAD | [`cipher::xchacha20poly1305`] (24-byte nonce) |
54//!
55//! In addition to these one-shot, function-oriented APIs, a
56//! stateful **streaming `Cipher` object** lives in
57//! [`cipher::ctx`]. It wraps the AES / DES / 3DES block ciphers
58//! with the ECB / CBC / CTR modes behind a uniform
59//! `init / update / finalize` cycle, with caller-provided output
60//! buffers (no allocation required) and configurable padding
61//! (`None`, `Pkcs7`, `Iso9797M1`, `Iso9797M2`, `AnsiX923`). AEAD
62//! modes intentionally stay function-oriented to avoid releasing
63//! unverified plaintext during streaming decryption.
64//!
65//! ## Message authentication codes ([`mac`])
66//!
67//! A streaming MAC object lives in [`mac::ctx`], exposing a
68//! uniform `init / update / sign | verify` cycle across four
69//! families:
70//!
71//! | Family | Algorithms | Standard |
72//! |--------------|------------------------------------------------------------------|-------------------|
73//! | HMAC | SHA-1, SHA-256, SHA-384, SHA-512, SHA3-256/384/512, RIPEMD-160 | RFC 2104, FIPS 198-1 |
74//! | CMAC | AES-128 / 192 / 256, Triple-DES | NIST SP 800-38B, RFC 4493 |
75//! | KMAC | KMAC128, KMAC256 | FIPS SP 800-185 |
76//! | GMAC | AES-128 / 192 / 256 | NIST SP 800-38D |
77//!
78//! Three init variants distinguish families that need different
79//! inputs: `init(key)` (HMAC, CMAC, KMAC default),
80//! `init_kmac(key, custom)` (KMAC with explicit customization
81//! string), and `init_with_nonce(key, nonce)` (GMAC, 12-byte
82//! unique nonce). `verify` accepts truncated tags and runs in
83//! constant time. Poly1305 is intentionally **not** routed
84//! through `Mac` because it is a one-time MAC and would be unsafe
85//! behind an "init then reuse" object.
86//!
87//! ## RSA ([`rsa`])
88//!
89//! | Padding | Module |
90//! |-----------------------------------|----------------------------------------------|
91//! | PKCS#1 v1.5 encryption + signature| [`rsa::pkcs1`] (8 hash functions supported) |
92//! | OAEP encryption (PKCS#1 v2.2) | [`rsa::oaep`] |
93//! | RSASSA-PSS signature (PKCS#1 v2.2)| [`rsa::pss`] |
94//!
95//! ## Elliptic curve cryptography ([`ecc`])
96//!
97//! ECDSA / ECDH / SEC1-compressed / signature DER are all unified
98//! behind a single [`ecc::curves::Curve`] trait, implemented by:
99//!
100//! | Wrapper | Curve |
101//! |-------------------------------------------|--------------------|
102//! | [`ecc::curves::P256`] | NIST P-256 |
103//! | [`ecc::curves::P384`] | NIST P-384 |
104//! | [`ecc::curves::P521`] | NIST P-521 |
105//! | [`ecc::curves::Secp256k1`] | secp256k1 (SECG) |
106//! | [`ecc::curves::BrainpoolP256r1`] | BSI / RFC 5639 |
107//! | [`ecc::curves::BrainpoolP384r1`] | BSI / RFC 5639 |
108//! | [`ecc::curves::BrainpoolP512r1`] | BSI / RFC 5639 |
109//!
110//! Edwards / Montgomery curves live in standalone modules because
111//! their algebraic shape doesn't fit the short-Weierstrass `Curve`
112//! trait:
113//!
114//! | Algorithm | Module |
115//! |-----------------------------------|-----------------------|
116//! | Ed25519, Ed25519ctx, Ed25519ph | [`ecc::eddsa`] |
117//! | Ed448, Ed448ctx | [`ecc::ed448`] |
118//! | X25519 ECDH (Curve25519) | [`ecc::x25519`] |
119//! | X448 ECDH (Curve448) | [`ecc::x448`] |
120//!
121//! # Cargo features
122//!
123//! | Feature | Default | Effect |
124//! |----------------------|---------|---------------------------------------------------------|
125//! | `std` | no | Reserved for future `no_std` work (currently a no-op). |
126//! | `rust-crypto-traits` | no | Pulls in `digest 0.10` / `cipher 0.4` / `signature 2.0` and activates the `bridge` module, which wraps every hash in a `digest::Digest` impl for ecosystem interop (HMAC, HKDF, PBKDF2, Argon2, ...). |
127//!
128//! Default builds are **zero-dependency** (only the workspace-local
129//! `silentops` crate). The `rust-crypto-traits` feature is opt-in
130//! and pulls in definitions only (no crypto code).
131//!
132//! # Quick start
133//!
134//! ```rust
135//! // SHA-256 one-shot hash
136//! use arcana::hash::sha256::Sha256;
137//! use arcana::Hasher;
138//! let digest = Sha256::hash(b"hello, arcana");
139//! assert_eq!(digest.len(), 32);
140//! ```
141//!
142//! ```rust
143//! // AES-128-GCM AEAD
144//! use arcana::cipher::aes::Aes128;
145//! use arcana::cipher::modes::Gcm;
146//! use arcana::BlockCipher;
147//!
148//! let key = [0x42u8; 16];
149//! let nonce = [0u8; 12];
150//! let cipher = Aes128::new(&key);
151//! let (ct, tag) = Gcm::encrypt(&cipher, &nonce, b"aad", b"plaintext");
152//! let pt = Gcm::decrypt(&cipher, &nonce, b"aad", &ct, &tag).unwrap();
153//! assert_eq!(pt, b"plaintext");
154//! ```
155//!
156//! ```rust
157//! // X25519 ECDH key exchange
158//! use arcana::ecc::x25519::{x25519_derive_public, x25519_ecdh};
159//! let alice_sk = [0x77u8; 32];
160//! let bob_sk = [0x88u8; 32];
161//! let alice_pk = x25519_derive_public(&alice_sk);
162//! let bob_pk = x25519_derive_public(&bob_sk);
163//! let shared_a = x25519_ecdh(&alice_sk, &bob_pk);
164//! let shared_b = x25519_ecdh(&bob_sk, &alice_pk);
165//! assert_eq!(shared_a, shared_b);
166//! ```
167//!
168//! # Examples
169//!
170//! ```sh
171//! cargo run -p krypteia-arcana --release --example hash_demo
172//! cargo run -p krypteia-arcana --release --example aes_demo
173//! cargo run -p krypteia-arcana --release --example rsa_demo
174//! cargo run -p krypteia-arcana --release --example ecdsa_demo
175//! cargo run -p krypteia-arcana --release --example eddsa_demo
176//! cargo run -p krypteia-arcana --release --example x25519_demo
177//! ```
178//!
179//! # Side-channel guarantees
180//!
181//! The full threat model and per-algorithm countermeasure roadmap
182//! lives in `arcana/doc/sca/` (HTML rendered by `./gendoc.sh
183//! arcana`). The roadmap items referenced here (`T1-A`, `T1-C`,
184//! `T2-D` …) are defined there. This section is a quick-reference
185//! summary; for evaluation evidence, refer to the annex.
186//!
187//! ## Always-on
188//!
189//! | Defence | Scope |
190//! |------------------------------------|----------------------------------------------------------------------------------------|
191//! | Constant-time tag comparison | All AEAD decrypt, MAC verify, ECDSA verify (via `silentops::ct_eq`) |
192//! | Constant-time field arithmetic | ECC (P-256, P-384, P-521, secp256k1, Brainpool), Ed25519, X25519, X448 |
193//! | **CT Montgomery ladder** | [`ecc::curve::scalar_mul_point`] — branch-free across all 7 short-Weierstrass curves |
194//! | `core::hint::black_box` shielding | [`ecc::field`] mask selects, to keep LLVM from recovering branches at `opt-level >= 2` |
195//! | No secret-dependent branches | Hash, symmetric, HMAC, CMAC, KMAC, GMAC |
196//! | RFC 6979 deterministic nonce | ECDSA — eliminates nonce-reuse attacks (but see fault-attack note below) |
197//! | `silentops::ct_zeroize` available | Caller can zeroize sensitive buffers explicitly |
198//!
199//! ## Open vulnerabilities (evaluation gaps — track in roadmap)
200//!
201//! | Primitive | Issue | Roadmap item |
202//! |---------------------|----------------------------------------------------------------------|--------------|
203//! | AES S-box | Table-based lookup — cache-line leaks on CPUs with shared L1 | `T1-A` (fixsliced AES port from Adomnicai-Peyrin TCHES 2021/1) |
204//! | RSA-CRT decrypt | Bellcore single-fault → factorisation of `N` | `T1-C` (Aumüller 2002, formally verified) |
205//! | RSA bigint | `mont_mul`, `cmp_to`, `mod_exp` not formally CT-audited | `T1-E` |
206//! | ECDSA / ECDH | Minerva-class bit-length leak audit not yet completed | `T1-B` |
207//! | Ed25519 / ECDSA-RFC 6979 | Single-fault on deterministic signing → key recovery | `T1-D` (hedged signatures, CFRG draft) |
208//! | HMAC-SHA-2 | Carry-based DPA breaks unmasked HMAC-SHA-2 in 30 K – 275 K traces | `T2-D` (first-order Boolean masking) |
209//! | DES / 3DES S-boxes | Table-based; legacy only | unscoped — avoid on SCA-sensitive targets |
210//!
211//! ## Not yet implemented
212//!
213//! - **Zeroize-on-Drop**: typed key wrappers ([`ecc::curves::SecretKey`],
214//! [`ecc::eddsa::Ed25519SecretKey`], [`rsa::rsa::RsaSecretKey`])
215//! currently **do not** implement `Drop` with
216//! `silentops::ct_zeroize`. Callers should zeroize sensitive
217//! buffers explicitly. Roadmap item `T2-E`.
218//! - **DPA / EM / multi-fault**: out of scope for the current
219//! evaluation profile; tier-4 items in the SCA annex.
220//!
221//! # Native API vs RustCrypto bridge
222//!
223//! By default, this crate exposes its own trait hierarchy
224//! ([`Hasher`], [`Xof`], [`BlockCipher`], ...). Enable
225//! `rust-crypto-traits` to additionally activate the `bridge`
226//! module which implements `digest::Digest` for the hash
227//! functions. The two universes are intentionally separate so the
228//! native modules never depend on a specific external crate
229//! version.
230
231// ============================================================
232// Our own trait definitions (zero-dependency)
233// ============================================================
234
235/// Trait for hash functions (fixed-output).
236pub trait Hasher {
237 /// Output size in bytes.
238 const OUTPUT_LEN: usize;
239 /// Block size in bytes (for HMAC computation).
240 const BLOCK_LEN: usize;
241
242 /// Create a new hasher instance.
243 fn new() -> Self;
244 /// Feed data into the hasher (can be called multiple times).
245 fn update(&mut self, data: &[u8]);
246 /// Finalize and return the digest. Consumes the hasher.
247 fn finalize(self) -> Vec<u8>;
248 /// Finalize into a caller-provided buffer (no allocation).
249 fn finalize_into(self, out: &mut [u8]);
250 /// One-shot: hash data and return the digest.
251 fn hash(data: &[u8]) -> Vec<u8>
252 where
253 Self: Sized,
254 {
255 let mut h = Self::new();
256 h.update(data);
257 h.finalize()
258 }
259}
260
261/// Trait for extendable-output functions (XOF) such as SHAKE128 / SHAKE256.
262///
263/// Unlike a fixed-output [`Hasher`], an XOF can be `squeeze`-d for any
264/// number of bytes after absorption is complete. The internal sponge
265/// state is mutated by every `squeeze` call, so successive squeezes
266/// extend the output stream rather than restart it.
267pub trait Xof {
268 /// Sponge rate (number of bytes absorbed per Keccak-f permutation).
269 const BLOCK_LEN: usize;
270
271 /// Create a fresh XOF instance with empty absorbed state.
272 fn new() -> Self;
273 /// Absorb additional input. May be called any number of times before
274 /// the first `squeeze`.
275 fn update(&mut self, data: &[u8]);
276 /// Squeeze `out.len()` bytes of output. Can be called multiple times;
277 /// successive calls extend the same output stream (they do not reset).
278 fn squeeze(&mut self, out: &mut [u8]);
279}
280
281// ------------------------------------------------------------
282// Bridge to the shared `krypteia-tessera` crate
283// ------------------------------------------------------------
284//
285// Every hash lives once, in `krypteia-tessera`. arcana re-exports those
286// types under `hash::*` (see `hash` module) and these blanket impls give
287// them arcana's richer, `Vec`-returning surface. The two trait families
288// are method-compatible; the only real difference is that arcana's
289// `finalize` allocates while the shared `Digest::finalize` writes into a
290// caller buffer. No hash algorithm is implemented twice.
291
292impl<T: tessera::Digest> Hasher for T {
293 const OUTPUT_LEN: usize = <T as tessera::Digest>::OUTPUT_LEN;
294 const BLOCK_LEN: usize = <T as tessera::Digest>::BLOCK_LEN;
295
296 fn new() -> Self {
297 <T as tessera::Digest>::new()
298 }
299
300 fn update(&mut self, data: &[u8]) {
301 <T as tessera::Digest>::update(self, data)
302 }
303
304 fn finalize(self) -> Vec<u8> {
305 let mut out = vec![0u8; <T as tessera::Digest>::OUTPUT_LEN];
306 <T as tessera::Digest>::finalize(self, &mut out);
307 out
308 }
309
310 fn finalize_into(self, out: &mut [u8]) {
311 <T as tessera::Digest>::finalize(self, out)
312 }
313}
314
315impl<T: tessera::Xof> Xof for T {
316 const BLOCK_LEN: usize = <T as tessera::Xof>::BLOCK_LEN;
317
318 fn new() -> Self {
319 <T as tessera::Xof>::new()
320 }
321
322 fn update(&mut self, data: &[u8]) {
323 <T as tessera::Xof>::update(self, data)
324 }
325
326 fn squeeze(&mut self, out: &mut [u8]) {
327 <T as tessera::Xof>::squeeze(self, out)
328 }
329}
330
331/// Trait for symmetric block ciphers operating on a fixed block size.
332///
333/// Implementors include the AES family ([`cipher::Aes`], [`cipher::Aes128`],
334/// [`cipher::Aes192`], [`cipher::Aes256`]) and the DES family
335/// ([`cipher::Des`], [`cipher::TripleDes`]).
336pub trait BlockCipher {
337 /// Block size in bytes (16 for AES, 8 for DES / 3DES).
338 const BLOCK_LEN: usize;
339 /// Key sizes supported by `new`, in bytes.
340 const KEY_LENS: &'static [usize];
341
342 /// Initialise the cipher with a key. The key length must be one of
343 /// the values listed in [`Self::KEY_LENS`].
344 fn new(key: &[u8]) -> Self;
345 /// Encrypt `block` in place. The slice must be at least
346 /// [`Self::BLOCK_LEN`] bytes long.
347 fn encrypt_block(&self, block: &mut [u8]);
348 /// Decrypt `block` in place. The slice must be at least
349 /// [`Self::BLOCK_LEN`] bytes long.
350 fn decrypt_block(&self, block: &mut [u8]);
351}
352
353/// Trait for digital signature schemes that produce fixed-shape keys
354/// and signatures (no per-call hash parameter).
355///
356/// **Note**: this trait is currently informational. Most signature
357/// schemes in this crate (ECDSA, EdDSA, RSA-PSS, RSA-PKCS1) take a
358/// hash function as a generic or runtime parameter and therefore do
359/// not implement this trait directly -- they expose their own
360/// curve/key types and sign/verify functions. The trait is kept as
361/// the canonical "shape" of the simplest signature scheme for
362/// future extensions and for documentation purposes.
363pub trait SignatureScheme {
364 /// Public key type for this scheme.
365 type PublicKey;
366 /// Secret key type for this scheme.
367 type SecretKey;
368 /// Signature type.
369 type Signature;
370
371 /// Generate a fresh key pair.
372 fn keygen() -> (Self::PublicKey, Self::SecretKey);
373 /// Sign a message.
374 fn sign(sk: &Self::SecretKey, message: &[u8]) -> Self::Signature;
375 /// Verify a signature against a message.
376 fn verify(pk: &Self::PublicKey, message: &[u8], sig: &Self::Signature) -> bool;
377}
378
379/// Trait for public-key encryption schemes.
380///
381/// Like [`SignatureScheme`], this trait is currently informational --
382/// the RSA encryption helpers in [`rsa::pkcs1`] and [`rsa::oaep`] take
383/// a parameter of type [`rsa::rsa::RsaPublicKey`] / `RsaSecretKey`
384/// directly rather than going through this trait, because OAEP and
385/// PKCS#1 v1.5 each take additional protocol parameters (label, RNG)
386/// that don't fit the simple shape below.
387pub trait PublicKeyEncryption {
388 /// Public key type.
389 type PublicKey;
390 /// Secret key type.
391 type SecretKey;
392
393 /// Generate a key pair of the given bit size.
394 fn keygen(bits: usize) -> (Self::PublicKey, Self::SecretKey);
395 /// Encrypt a plaintext under the public key.
396 fn encrypt(pk: &Self::PublicKey, plaintext: &[u8]) -> Vec<u8>;
397 /// Decrypt a ciphertext with the secret key. Returns `None` if the
398 /// padding does not validate.
399 fn decrypt(sk: &Self::SecretKey, ciphertext: &[u8]) -> Option<Vec<u8>>;
400}
401
402// ============================================================
403// Modules
404// ============================================================
405
406/// Hash functions: SHA-1, SHA-2, SHA-3, RIPEMD-160.
407pub mod hash;
408
409/// Symmetric ciphers: AES, DES, 3DES.
410pub mod cipher;
411
412/// Elliptic curve cryptography: ECDSA, EdDSA.
413pub mod ecc;
414
415/// RSA encryption and signatures.
416pub mod rsa;
417
418/// Message authentication codes (HMAC, CMAC, KMAC, GMAC).
419pub mod mac;
420
421/// Deterministic random bit generators (NIST SP 800-90A Rev.1):
422/// Hash_DRBG, HMAC_DRBG, CTR_DRBG.
423pub mod drbg;
424
425/// Key serialization: DER, PEM, PKCS#1, PKCS#8, SEC1, SPKI.
426pub mod encoding;
427
428/// RustCrypto trait bridges (feature-gated).
429#[cfg(feature = "rust-crypto-traits")]
430pub mod bridge;