Skip to main content

arcana/
lib.rs

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