Skip to main content

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;