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}