Skip to main content

ic_core/
traits.rs

1//! The algorithm contracts every IronCrypto primitive implements.
2//!
3//! Two properties make these traits agent-friendly:
4//!
5//! 1. **Self-describing.** Every implementation carries an [`Algorithm::ID`]
6//!    that resolves to an entry in the ontology, so a value at runtime can be
7//!    traced back to its full machine-readable specification.
8//! 2. **Total.** Nothing panics on bad input; sizes are surfaced as associated
9//!    constants *and* as ontology metadata, so an agent can validate a call
10//!    before making it.
11
12use crate::Result;
13
14/// Links a concrete implementation to its ontology entry.
15///
16/// `ID` is the stable ontology identifier (for example `"sha2-256"`), and is
17/// the join key between runtime types and the machine-readable catalog exposed
18/// by `ic-ontology`.
19pub trait Algorithm {
20    /// Stable ontology identifier for this algorithm.
21    const ID: &'static str;
22    /// Human-facing display name (for example `"SHA-256"`).
23    const NAME: &'static str;
24}
25
26/// A cryptographic hash function with a fixed-length output.
27///
28/// Producing output can fail: [`Digest::finalize`] and [`Digest::digest`]
29/// refuse with [`ErrorKind::ModuleErrorState`](crate::ErrorKind) once the
30/// module is in its error state, and, in a build with
31/// `--cfg ic_approved_only`, before `ic_fips::initialize` has passed the
32/// self-tests. Making a hasher and absorbing input produce nothing, and do not
33/// refuse.
34///
35/// Implemented for every type that implements [`internal::Digest`], which is
36/// how the hash functions are written.
37pub trait Digest: Algorithm + Clone + Default {
38    /// The fixed-size output buffer type, e.g. `[u8; 32]`.
39    type Output: AsRef<[u8]> + AsMut<[u8]> + Copy;
40
41    /// Output length in bytes.
42    const OUTPUT_LEN: usize;
43    /// Internal block (rate) length in bytes — required by HMAC and KMAC.
44    const BLOCK_LEN: usize;
45
46    /// Create a fresh, empty hasher.
47    fn new() -> Self;
48
49    /// Absorb more input. May be called any number of times.
50    fn update(&mut self, data: &[u8]);
51
52    /// Consume the hasher and produce the digest.
53    fn finalize(self) -> Result<Self::Output>;
54
55    /// One-shot convenience: hash `data` in a single call.
56    fn digest(data: &[u8]) -> Result<Self::Output> {
57        let mut h = Self::new();
58        h.update(data);
59        h.finalize()
60    }
61}
62
63/// An extendable-output function (XOF) such as SHAKE128 / SHAKE256.
64///
65/// [`Xof::finalize_xof`] refuses as [`Digest::finalize`] does.
66pub trait Xof: Algorithm + Clone + Default {
67    /// Rate in bytes.
68    const BLOCK_LEN: usize;
69
70    /// Absorb more input.
71    fn update(&mut self, data: &[u8]);
72
73    /// Squeeze `out.len()` bytes of output, consuming the state.
74    fn finalize_xof(self, out: &mut [u8]) -> Result<()>;
75}
76
77impl<T: internal::Digest> Digest for T {
78    type Output = <T as internal::Digest>::Output;
79    const OUTPUT_LEN: usize = <T as internal::Digest>::OUTPUT_LEN;
80    const BLOCK_LEN: usize = <T as internal::Digest>::BLOCK_LEN;
81
82    fn new() -> Self {
83        <T as internal::Digest>::new()
84    }
85
86    fn update(&mut self, data: &[u8]) {
87        <T as internal::Digest>::update(self, data)
88    }
89
90    fn finalize(self) -> Result<Self::Output> {
91        crate::module::operational()?;
92        Ok(<T as internal::Digest>::finalize(self))
93    }
94
95    /// The implementation's own one-shot path, which may be faster than
96    /// `new`, `update` and `finalize`, behind the same refusal.
97    fn digest(data: &[u8]) -> Result<Self::Output> {
98        crate::module::operational()?;
99        Ok(<T as internal::Digest>::digest(data))
100    }
101}
102
103impl<T: internal::Xof> Xof for T {
104    const BLOCK_LEN: usize = <T as internal::Xof>::BLOCK_LEN;
105
106    fn update(&mut self, data: &[u8]) {
107        <T as internal::Xof>::update(self, data)
108    }
109
110    fn finalize_xof(self, out: &mut [u8]) -> Result<()> {
111        crate::module::operational()?;
112        <T as internal::Xof>::finalize_xof(self, out);
113        Ok(())
114    }
115}
116
117/// The hash functions and MACs as the rest of this library calls them,
118/// without the refusal on output. **Not API**, and not covered by semver.
119///
120/// A FIPS module has to refuse its services once it has failed; it does not
121/// have to refuse each step inside one. HMAC, HKDF, the DRBGs, ECDSA, RSA,
122/// ML-KEM, ML-DSA, SLH-DSA, LMS and the rest each refuse at their own entry,
123/// and then hash on the way to their answer through these traits, without a
124/// second check per block. Everything outside the module reaches a hash
125/// through [`Digest`] and [`Xof`], which refuse.
126///
127/// Nothing stops another crate from naming this module. A caller who does has
128/// stepped inside the boundary on purpose, and gets a hash that does not
129/// refuse.
130#[doc(hidden)]
131pub mod internal {
132    use super::Algorithm;
133
134    /// [`super::Digest`], without the refusal.
135    pub trait Digest: Algorithm + Clone + Default {
136        /// The fixed-size output buffer type.
137        type Output: AsRef<[u8]> + AsMut<[u8]> + Copy;
138        /// Output length in bytes.
139        const OUTPUT_LEN: usize;
140        /// Internal block (rate) length in bytes.
141        const BLOCK_LEN: usize;
142
143        /// Create a fresh, empty hasher.
144        fn new() -> Self {
145            Self::default()
146        }
147
148        /// Absorb more input.
149        fn update(&mut self, data: &[u8]);
150
151        /// Consume the hasher and produce the digest.
152        fn finalize(self) -> Self::Output;
153
154        /// Hash `data` in a single call.
155        fn digest(data: &[u8]) -> Self::Output {
156            let mut h = Self::new();
157            h.update(data);
158            h.finalize()
159        }
160    }
161
162    /// [`super::Mac`], without the refusal in `finalize`. `new` still
163    /// refuses, as every implementation's does.
164    pub trait Mac: Algorithm + Clone {
165        /// The fixed-size tag type.
166        type Tag: AsRef<[u8]> + AsMut<[u8]> + Copy;
167        /// Tag length in bytes.
168        const TAG_LEN: usize;
169
170        /// Create a MAC instance from a key.
171        fn new(key: &[u8]) -> crate::Result<Self>
172        where
173            Self: Sized;
174
175        /// Absorb more input.
176        fn update(&mut self, data: &[u8]);
177
178        /// Consume the instance and produce the tag.
179        fn finalize(self) -> Self::Tag;
180
181        /// One-shot authentication.
182        fn mac(key: &[u8], data: &[u8]) -> crate::Result<Self::Tag>
183        where
184            Self: Sized,
185        {
186            let mut m = Self::new(key)?;
187            m.update(data);
188            Ok(m.finalize())
189        }
190    }
191
192    /// [`super::Xof`], without the refusal.
193    pub trait Xof: Algorithm + Clone + Default {
194        /// Rate in bytes.
195        const BLOCK_LEN: usize;
196
197        /// Absorb more input.
198        fn update(&mut self, data: &[u8]);
199
200        /// Squeeze `out.len()` bytes of output, consuming the state.
201        fn finalize_xof(self, out: &mut [u8]);
202    }
203}
204
205/// A keyed message authentication code.
206///
207/// [`Mac::new`] and [`Mac::finalize`] refuse with
208/// [`ErrorKind::ModuleErrorState`](crate::ErrorKind) once the module is in its
209/// error state, so a MAC keyed before the failure produces no tag after it.
210///
211/// Implemented for every type that implements [`internal::Mac`].
212pub trait Mac: Algorithm + Clone {
213    /// The fixed-size tag type.
214    type Tag: AsRef<[u8]> + AsMut<[u8]> + Copy;
215
216    /// Tag length in bytes.
217    const TAG_LEN: usize;
218
219    /// Create a MAC instance from a key of any length the algorithm accepts.
220    fn new(key: &[u8]) -> Result<Self>
221    where
222        Self: Sized;
223
224    /// Absorb more input.
225    fn update(&mut self, data: &[u8]);
226
227    /// Consume the instance and produce the tag.
228    fn finalize(self) -> Result<Self::Tag>;
229
230    /// One-shot authentication.
231    fn mac(key: &[u8], data: &[u8]) -> Result<Self::Tag>
232    where
233        Self: Sized,
234    {
235        let mut m = Self::new(key)?;
236        m.update(data);
237        m.finalize()
238    }
239
240    /// Constant-time tag verification.
241    ///
242    /// Returns [`crate::ErrorKind::AuthenticationFailed`] on mismatch and
243    /// never reveals *where* the tags diverged.
244    fn verify(key: &[u8], data: &[u8], tag: &[u8]) -> Result<()>
245    where
246        Self: Sized,
247    {
248        let expected = Self::mac(key, data)?;
249        if crate::ct::verify(expected.as_ref(), tag) {
250            Ok(())
251        } else {
252            Err(crate::err!(AuthenticationFailed, "mac tag"))
253        }
254    }
255}
256
257impl<T: internal::Mac> Mac for T {
258    type Tag = <T as internal::Mac>::Tag;
259    const TAG_LEN: usize = <T as internal::Mac>::TAG_LEN;
260
261    fn new(key: &[u8]) -> Result<Self> {
262        <T as internal::Mac>::new(key)
263    }
264
265    fn update(&mut self, data: &[u8]) {
266        <T as internal::Mac>::update(self, data)
267    }
268
269    fn finalize(self) -> Result<Self::Tag> {
270        crate::module::operational()?;
271        Ok(<T as internal::Mac>::finalize(self))
272    }
273
274    /// The implementation's own one-shot path, behind the same refusal.
275    ///
276    /// Through 0.3.0 this was the trait's default -- `new`, `update` and
277    /// `finalize` -- so a caller through this trait never reached the
278    /// one-shot paths the implementations provide, HMAC's among them, which
279    /// the library's own callers, through the internal trait, did reach.
280    fn mac(key: &[u8], data: &[u8]) -> Result<Self::Tag> {
281        crate::module::operational()?;
282        <T as internal::Mac>::mac(key, data)
283    }
284}
285
286/// A fixed-width block cipher primitive (raw ECB core, for use inside modes).
287pub trait BlockCipher: Algorithm {
288    /// Block size in bytes.
289    const BLOCK_LEN: usize;
290    /// Accepted key length in bytes.
291    const KEY_LEN: usize;
292
293    /// Expand a key into a round-key schedule.
294    fn new(key: &[u8]) -> Result<Self>
295    where
296        Self: Sized;
297
298    /// Encrypt a single block in place.
299    fn encrypt_block(&self, block: &mut [u8]) -> Result<()>;
300
301    /// Decrypt a single block in place.
302    fn decrypt_block(&self, block: &mut [u8]) -> Result<()>;
303
304    /// Encrypt a whole number of blocks in place.
305    ///
306    /// The default implementation loops over [`encrypt_block`][Self::encrypt_block].
307    /// Backends with instruction-level parallelism override it: AES-NI has a
308    /// pipelined round instruction, so encrypting eight independent blocks at
309    /// once is several times faster than eight sequential calls. Counter-based
310    /// modes route through here for exactly that reason.
311    ///
312    /// Returns [`crate::ErrorKind::InvalidLength`] if `data` is not a whole
313    /// number of blocks.
314    fn encrypt_blocks(&self, data: &mut [u8]) -> Result<()> {
315        if !data.len().is_multiple_of(Self::BLOCK_LEN) {
316            return Err(crate::err!(InvalidLength, "batch must be block-aligned"));
317        }
318        for block in data.chunks_mut(Self::BLOCK_LEN) {
319            self.encrypt_block(block)?;
320        }
321        Ok(())
322    }
323}
324
325/// An authenticated cipher with associated data.
326pub trait Aead: Algorithm {
327    /// Key length in bytes.
328    const KEY_LEN: usize;
329    /// Nonce length in bytes.
330    const NONCE_LEN: usize;
331    /// Authentication tag length in bytes.
332    const TAG_LEN: usize;
333
334    /// Bind a key to a cipher instance.
335    fn new(key: &[u8]) -> Result<Self>
336    where
337        Self: Sized;
338
339    /// Encrypt `in_out` in place and write the tag to `tag`.
340    fn seal_detached(
341        &self,
342        nonce: &[u8],
343        aad: &[u8],
344        in_out: &mut [u8],
345        tag: &mut [u8],
346    ) -> Result<()>;
347
348    /// Verify `tag` and decrypt `in_out` in place.
349    ///
350    /// On failure `in_out` is zeroized before returning, so a caller that
351    /// ignores the error cannot expose unauthenticated plaintext.
352    fn open_detached(&self, nonce: &[u8], aad: &[u8], in_out: &mut [u8], tag: &[u8]) -> Result<()>;
353}
354
355/// A key derivation function that expands keying material to a requested length.
356pub trait Kdf: Algorithm {
357    /// Derive `out.len()` bytes from the supplied inputs.
358    fn derive(secret: &[u8], salt: &[u8], info: &[u8], out: &mut [u8]) -> Result<()>;
359}
360
361/// A deterministic random bit generator (SP 800-90A).
362pub trait Drbg: Algorithm {
363    /// Instantiate from entropy input, a nonce, and an optional personalization
364    /// string.
365    fn instantiate(entropy: &[u8], nonce: &[u8], personalization: &[u8]) -> Result<Self>
366    where
367        Self: Sized;
368
369    /// Reseed with fresh entropy and optional additional input.
370    fn reseed(&mut self, entropy: &[u8], additional: &[u8]) -> Result<()>;
371
372    /// Fill `out` with generated bits, honouring the reseed interval.
373    fn generate(&mut self, additional: &[u8], out: &mut [u8]) -> Result<()>;
374}
375
376/// A source of random bytes suitable for key generation.
377pub trait RandomSource {
378    /// Fill `out` with random bytes.
379    fn fill(&mut self, out: &mut [u8]) -> Result<()>;
380}
381
382/// A Diffie-Hellman style key agreement scheme.
383pub trait KeyAgreement: Algorithm {
384    /// Private key (scalar) length in bytes.
385    const PRIVATE_KEY_LEN: usize;
386    /// Public key (encoded point) length in bytes.
387    const PUBLIC_KEY_LEN: usize;
388    /// Shared secret length in bytes.
389    const SHARED_SECRET_LEN: usize;
390
391    /// Compute the public key for a private key.
392    fn public_key(private_key: &[u8], out: &mut [u8]) -> Result<()>;
393
394    /// Compute the shared secret from our private key and their public key.
395    fn agree(private_key: &[u8], peer_public_key: &[u8], out: &mut [u8]) -> Result<()>;
396}
397
398/// A digital signature scheme.
399pub trait SignatureScheme: Algorithm {
400    /// Seed / private key length in bytes.
401    const PRIVATE_KEY_LEN: usize;
402    /// Public key length in bytes.
403    const PUBLIC_KEY_LEN: usize;
404    /// Signature length in bytes.
405    const SIGNATURE_LEN: usize;
406
407    /// Derive the public key from a private key.
408    fn public_key(private_key: &[u8], out: &mut [u8]) -> Result<()>;
409
410    /// Sign `message`, writing exactly [`Self::SIGNATURE_LEN`] bytes.
411    fn sign(private_key: &[u8], message: &[u8], signature: &mut [u8]) -> Result<()>;
412
413    /// Verify a signature, returning [`crate::ErrorKind::AuthenticationFailed`]
414    /// when it does not check out.
415    fn verify(public_key: &[u8], message: &[u8], signature: &[u8]) -> Result<()>;
416}
417
418/// A known-answer test an implementation runs to satisfy FIPS 140-3 CAST
419/// requirements.
420///
421/// Implemented by every approved algorithm so `ic-fips` can enumerate and drive
422/// the full self-test suite without hard-coding a list.
423pub trait SelfTest {
424    /// Run the algorithm's known-answer test.
425    ///
426    /// Returns [`crate::ErrorKind::SelfTestFailed`] if the computed value does
427    /// not match the embedded vector.
428    fn self_test() -> Result<()>;
429}