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
96impl<T: internal::Xof> Xof for T {
97 const BLOCK_LEN: usize = <T as internal::Xof>::BLOCK_LEN;
98
99 fn update(&mut self, data: &[u8]) {
100 <T as internal::Xof>::update(self, data)
101 }
102
103 fn finalize_xof(self, out: &mut [u8]) -> Result<()> {
104 crate::module::operational()?;
105 <T as internal::Xof>::finalize_xof(self, out);
106 Ok(())
107 }
108}
109
110/// The hash functions and MACs as the rest of this library calls them,
111/// without the refusal on output. **Not API**, and not covered by semver.
112///
113/// A FIPS module has to refuse its services once it has failed; it does not
114/// have to refuse each step inside one. HMAC, HKDF, the DRBGs, ECDSA, RSA,
115/// ML-KEM, ML-DSA, SLH-DSA, LMS and the rest each refuse at their own entry,
116/// and then hash on the way to their answer through these traits, without a
117/// second check per block. Everything outside the module reaches a hash
118/// through [`Digest`] and [`Xof`], which refuse.
119///
120/// Nothing stops another crate from naming this module. A caller who does has
121/// stepped inside the boundary on purpose, and gets a hash that does not
122/// refuse.
123#[doc(hidden)]
124pub mod internal {
125 use super::Algorithm;
126
127 /// [`super::Digest`], without the refusal.
128 pub trait Digest: Algorithm + Clone + Default {
129 /// The fixed-size output buffer type.
130 type Output: AsRef<[u8]> + AsMut<[u8]> + Copy;
131 /// Output length in bytes.
132 const OUTPUT_LEN: usize;
133 /// Internal block (rate) length in bytes.
134 const BLOCK_LEN: usize;
135
136 /// Create a fresh, empty hasher.
137 fn new() -> Self {
138 Self::default()
139 }
140
141 /// Absorb more input.
142 fn update(&mut self, data: &[u8]);
143
144 /// Consume the hasher and produce the digest.
145 fn finalize(self) -> Self::Output;
146
147 /// Hash `data` in a single call.
148 fn digest(data: &[u8]) -> Self::Output {
149 let mut h = Self::new();
150 h.update(data);
151 h.finalize()
152 }
153 }
154
155 /// [`super::Mac`], without the refusal in `finalize`. `new` still
156 /// refuses, as every implementation's does.
157 pub trait Mac: Algorithm + Clone {
158 /// The fixed-size tag type.
159 type Tag: AsRef<[u8]> + AsMut<[u8]> + Copy;
160 /// Tag length in bytes.
161 const TAG_LEN: usize;
162
163 /// Create a MAC instance from a key.
164 fn new(key: &[u8]) -> crate::Result<Self>
165 where
166 Self: Sized;
167
168 /// Absorb more input.
169 fn update(&mut self, data: &[u8]);
170
171 /// Consume the instance and produce the tag.
172 fn finalize(self) -> Self::Tag;
173
174 /// One-shot authentication.
175 fn mac(key: &[u8], data: &[u8]) -> crate::Result<Self::Tag>
176 where
177 Self: Sized,
178 {
179 let mut m = Self::new(key)?;
180 m.update(data);
181 Ok(m.finalize())
182 }
183 }
184
185 /// [`super::Xof`], without the refusal.
186 pub trait Xof: Algorithm + Clone + Default {
187 /// Rate in bytes.
188 const BLOCK_LEN: usize;
189
190 /// Absorb more input.
191 fn update(&mut self, data: &[u8]);
192
193 /// Squeeze `out.len()` bytes of output, consuming the state.
194 fn finalize_xof(self, out: &mut [u8]);
195 }
196}
197
198/// A keyed message authentication code.
199///
200/// [`Mac::new`] and [`Mac::finalize`] refuse with
201/// [`ErrorKind::ModuleErrorState`](crate::ErrorKind) once the module is in its
202/// error state, so a MAC keyed before the failure produces no tag after it.
203///
204/// Implemented for every type that implements [`internal::Mac`].
205pub trait Mac: Algorithm + Clone {
206 /// The fixed-size tag type.
207 type Tag: AsRef<[u8]> + AsMut<[u8]> + Copy;
208
209 /// Tag length in bytes.
210 const TAG_LEN: usize;
211
212 /// Create a MAC instance from a key of any length the algorithm accepts.
213 fn new(key: &[u8]) -> Result<Self>
214 where
215 Self: Sized;
216
217 /// Absorb more input.
218 fn update(&mut self, data: &[u8]);
219
220 /// Consume the instance and produce the tag.
221 fn finalize(self) -> Result<Self::Tag>;
222
223 /// One-shot authentication.
224 fn mac(key: &[u8], data: &[u8]) -> Result<Self::Tag>
225 where
226 Self: Sized,
227 {
228 let mut m = Self::new(key)?;
229 m.update(data);
230 m.finalize()
231 }
232
233 /// Constant-time tag verification.
234 ///
235 /// Returns [`crate::ErrorKind::AuthenticationFailed`] on mismatch and
236 /// never reveals *where* the tags diverged.
237 fn verify(key: &[u8], data: &[u8], tag: &[u8]) -> Result<()>
238 where
239 Self: Sized,
240 {
241 let expected = Self::mac(key, data)?;
242 if crate::ct::verify(expected.as_ref(), tag) {
243 Ok(())
244 } else {
245 Err(crate::err!(AuthenticationFailed, "mac tag"))
246 }
247 }
248}
249
250impl<T: internal::Mac> Mac for T {
251 type Tag = <T as internal::Mac>::Tag;
252 const TAG_LEN: usize = <T as internal::Mac>::TAG_LEN;
253
254 fn new(key: &[u8]) -> Result<Self> {
255 <T as internal::Mac>::new(key)
256 }
257
258 fn update(&mut self, data: &[u8]) {
259 <T as internal::Mac>::update(self, data)
260 }
261
262 fn finalize(self) -> Result<Self::Tag> {
263 crate::module::operational()?;
264 Ok(<T as internal::Mac>::finalize(self))
265 }
266}
267
268/// A fixed-width block cipher primitive (raw ECB core, for use inside modes).
269pub trait BlockCipher: Algorithm {
270 /// Block size in bytes.
271 const BLOCK_LEN: usize;
272 /// Accepted key length in bytes.
273 const KEY_LEN: usize;
274
275 /// Expand a key into a round-key schedule.
276 fn new(key: &[u8]) -> Result<Self>
277 where
278 Self: Sized;
279
280 /// Encrypt a single block in place.
281 fn encrypt_block(&self, block: &mut [u8]) -> Result<()>;
282
283 /// Decrypt a single block in place.
284 fn decrypt_block(&self, block: &mut [u8]) -> Result<()>;
285
286 /// Encrypt a whole number of blocks in place.
287 ///
288 /// The default implementation loops over [`encrypt_block`][Self::encrypt_block].
289 /// Backends with instruction-level parallelism override it: AES-NI has a
290 /// pipelined round instruction, so encrypting eight independent blocks at
291 /// once is several times faster than eight sequential calls. Counter-based
292 /// modes route through here for exactly that reason.
293 ///
294 /// Returns [`crate::ErrorKind::InvalidLength`] if `data` is not a whole
295 /// number of blocks.
296 fn encrypt_blocks(&self, data: &mut [u8]) -> Result<()> {
297 if !data.len().is_multiple_of(Self::BLOCK_LEN) {
298 return Err(crate::err!(InvalidLength, "batch must be block-aligned"));
299 }
300 for block in data.chunks_mut(Self::BLOCK_LEN) {
301 self.encrypt_block(block)?;
302 }
303 Ok(())
304 }
305}
306
307/// An authenticated cipher with associated data.
308pub trait Aead: Algorithm {
309 /// Key length in bytes.
310 const KEY_LEN: usize;
311 /// Nonce length in bytes.
312 const NONCE_LEN: usize;
313 /// Authentication tag length in bytes.
314 const TAG_LEN: usize;
315
316 /// Bind a key to a cipher instance.
317 fn new(key: &[u8]) -> Result<Self>
318 where
319 Self: Sized;
320
321 /// Encrypt `in_out` in place and write the tag to `tag`.
322 fn seal_detached(
323 &self,
324 nonce: &[u8],
325 aad: &[u8],
326 in_out: &mut [u8],
327 tag: &mut [u8],
328 ) -> Result<()>;
329
330 /// Verify `tag` and decrypt `in_out` in place.
331 ///
332 /// On failure `in_out` is zeroized before returning, so a caller that
333 /// ignores the error cannot expose unauthenticated plaintext.
334 fn open_detached(&self, nonce: &[u8], aad: &[u8], in_out: &mut [u8], tag: &[u8]) -> Result<()>;
335}
336
337/// A key derivation function that expands keying material to a requested length.
338pub trait Kdf: Algorithm {
339 /// Derive `out.len()` bytes from the supplied inputs.
340 fn derive(secret: &[u8], salt: &[u8], info: &[u8], out: &mut [u8]) -> Result<()>;
341}
342
343/// A deterministic random bit generator (SP 800-90A).
344pub trait Drbg: Algorithm {
345 /// Instantiate from entropy input, a nonce, and an optional personalization
346 /// string.
347 fn instantiate(entropy: &[u8], nonce: &[u8], personalization: &[u8]) -> Result<Self>
348 where
349 Self: Sized;
350
351 /// Reseed with fresh entropy and optional additional input.
352 fn reseed(&mut self, entropy: &[u8], additional: &[u8]) -> Result<()>;
353
354 /// Fill `out` with generated bits, honouring the reseed interval.
355 fn generate(&mut self, additional: &[u8], out: &mut [u8]) -> Result<()>;
356}
357
358/// A source of random bytes suitable for key generation.
359pub trait RandomSource {
360 /// Fill `out` with random bytes.
361 fn fill(&mut self, out: &mut [u8]) -> Result<()>;
362}
363
364/// A Diffie-Hellman style key agreement scheme.
365pub trait KeyAgreement: Algorithm {
366 /// Private key (scalar) length in bytes.
367 const PRIVATE_KEY_LEN: usize;
368 /// Public key (encoded point) length in bytes.
369 const PUBLIC_KEY_LEN: usize;
370 /// Shared secret length in bytes.
371 const SHARED_SECRET_LEN: usize;
372
373 /// Compute the public key for a private key.
374 fn public_key(private_key: &[u8], out: &mut [u8]) -> Result<()>;
375
376 /// Compute the shared secret from our private key and their public key.
377 fn agree(private_key: &[u8], peer_public_key: &[u8], out: &mut [u8]) -> Result<()>;
378}
379
380/// A digital signature scheme.
381pub trait SignatureScheme: Algorithm {
382 /// Seed / private key length in bytes.
383 const PRIVATE_KEY_LEN: usize;
384 /// Public key length in bytes.
385 const PUBLIC_KEY_LEN: usize;
386 /// Signature length in bytes.
387 const SIGNATURE_LEN: usize;
388
389 /// Derive the public key from a private key.
390 fn public_key(private_key: &[u8], out: &mut [u8]) -> Result<()>;
391
392 /// Sign `message`, writing exactly [`Self::SIGNATURE_LEN`] bytes.
393 fn sign(private_key: &[u8], message: &[u8], signature: &mut [u8]) -> Result<()>;
394
395 /// Verify a signature, returning [`crate::ErrorKind::AuthenticationFailed`]
396 /// when it does not check out.
397 fn verify(public_key: &[u8], message: &[u8], signature: &[u8]) -> Result<()>;
398}
399
400/// A known-answer test an implementation runs to satisfy FIPS 140-3 CAST
401/// requirements.
402///
403/// Implemented by every approved algorithm so `ic-fips` can enumerate and drive
404/// the full self-test suite without hard-coding a list.
405pub trait SelfTest {
406 /// Run the algorithm's known-answer test.
407 ///
408 /// Returns [`crate::ErrorKind::SelfTestFailed`] if the computed value does
409 /// not match the embedded vector.
410 fn self_test() -> Result<()>;
411}