argon2_rust/params.rs
1//! Limits, [`Algorithm`], [`Version`] and [`Params`].
2//!
3//! Every constant here is transcribed from `phc-winner-argon2/include/argon2.h`
4//! and `phc-winner-argon2/src/core.h`. [`validate_inputs`] reproduces
5//! `validate_inputs()` from `src/core.c` **in the same order**, because the
6//! order decides which error code surfaces first.
7
8use crate::error::Error;
9
10// ---------------------------------------------------------------------------
11// Limits from include/argon2.h
12// ---------------------------------------------------------------------------
13
14/// `ARGON2_MIN_LANES`.
15pub const MIN_LANES: u32 = 1;
16/// `ARGON2_MAX_LANES`.
17pub const MAX_LANES: u32 = 0x00FF_FFFF;
18
19/// `ARGON2_MIN_THREADS`.
20pub const MIN_THREADS: u32 = 1;
21/// `ARGON2_MAX_THREADS`.
22pub const MAX_THREADS: u32 = 0x00FF_FFFF;
23
24/// `ARGON2_SYNC_POINTS`: synchronisation points between lanes per pass.
25pub const SYNC_POINTS: u32 = 4;
26
27/// `ARGON2_MIN_OUTLEN`.
28pub const MIN_OUTLEN: u32 = 4;
29/// `ARGON2_MAX_OUTLEN`.
30pub const MAX_OUTLEN: u32 = 0xFFFF_FFFF;
31
32/// `ARGON2_MIN_MEMORY` = `2 * ARGON2_SYNC_POINTS` (two blocks per slice).
33pub const MIN_MEMORY: u32 = 2 * SYNC_POINTS;
34
35/// `ARGON2_MAX_MEMORY_BITS` = `min(32, sizeof(void*) * CHAR_BIT - 10 - 1)`.
36///
37/// 32 on a 64-bit target, 21 on a 32-bit target.
38pub const MAX_MEMORY_BITS: u32 = {
39 let ptr_bits = (size_of::<*const u8>() * 8) as u32;
40 let bits = ptr_bits - 10 - 1;
41 if bits < 32 { bits } else { 32 }
42};
43
44/// `ARGON2_MAX_MEMORY` = `min(0xFFFFFFFF, 1 << ARGON2_MAX_MEMORY_BITS)`.
45///
46/// `0xFFFF_FFFF` on a 64-bit target, `0x0020_0000` on a 32-bit target.
47/// Verified against the C preprocessor on `aarch64-apple-darwin`.
48pub const MAX_MEMORY: u32 = {
49 let candidate: u64 = 1u64 << MAX_MEMORY_BITS;
50 if candidate < 0xFFFF_FFFF {
51 candidate as u32
52 } else {
53 0xFFFF_FFFF
54 }
55};
56
57/// `ARGON2_MIN_TIME`.
58pub const MIN_TIME: u32 = 1;
59/// `ARGON2_MAX_TIME`.
60pub const MAX_TIME: u32 = 0xFFFF_FFFF;
61
62/// `ARGON2_MIN_PWD_LENGTH`.
63pub const MIN_PWD_LENGTH: u32 = 0;
64/// `ARGON2_MAX_PWD_LENGTH`.
65pub const MAX_PWD_LENGTH: u32 = 0xFFFF_FFFF;
66
67/// `ARGON2_MIN_AD_LENGTH`.
68pub const MIN_AD_LENGTH: u32 = 0;
69/// `ARGON2_MAX_AD_LENGTH`.
70pub const MAX_AD_LENGTH: u32 = 0xFFFF_FFFF;
71
72/// `ARGON2_MIN_SALT_LENGTH`.
73pub const MIN_SALT_LENGTH: u32 = 8;
74/// `ARGON2_MAX_SALT_LENGTH`.
75pub const MAX_SALT_LENGTH: u32 = 0xFFFF_FFFF;
76
77/// `ARGON2_MIN_SECRET`.
78pub const MIN_SECRET: u32 = 0;
79/// `ARGON2_MAX_SECRET`.
80pub const MAX_SECRET: u32 = 0xFFFF_FFFF;
81
82// ---------------------------------------------------------------------------
83// Internal constants from src/core.h
84// ---------------------------------------------------------------------------
85
86/// `ARGON2_BLOCK_SIZE`: memory block size in bytes.
87pub const BLOCK_SIZE: usize = 1024;
88/// `ARGON2_QWORDS_IN_BLOCK`: 64-bit words per block.
89pub const QWORDS_IN_BLOCK: usize = BLOCK_SIZE / 8;
90/// `ARGON2_OWORDS_IN_BLOCK`: 128-bit lanes per block (SSE2).
91pub const OWORDS_IN_BLOCK: usize = BLOCK_SIZE / 16;
92/// `ARGON2_HWORDS_IN_BLOCK`: 256-bit lanes per block (AVX2).
93pub const HWORDS_IN_BLOCK: usize = BLOCK_SIZE / 32;
94/// `ARGON2_512BIT_WORDS_IN_BLOCK`: 512-bit lanes per block (AVX-512).
95pub const BITS512_WORDS_IN_BLOCK: usize = BLOCK_SIZE / 64;
96
97/// `ARGON2_ADDRESSES_IN_BLOCK`: pseudo-random values one address block holds.
98pub const ADDRESSES_IN_BLOCK: usize = 128;
99
100/// `ARGON2_PREHASH_DIGEST_LENGTH`: length of `H0`.
101pub const PREHASH_DIGEST_LENGTH: usize = 64;
102/// `ARGON2_PREHASH_SEED_LENGTH`: `H0` plus the 4-byte block index and 4-byte lane index.
103pub const PREHASH_SEED_LENGTH: usize = 72;
104
105// ---------------------------------------------------------------------------
106// Limits from src/encoding.h
107// ---------------------------------------------------------------------------
108//
109// Mirrored for completeness, and unused — the C defines all three in
110// `encoding.h:22-24` and then never reads them, so `decode_string` here does
111// not either. Keeping them (rather than dropping them) is what makes the
112// header-for-header correspondence with the C checkable; do not add a use for
113// them without checking the C grew one first.
114//
115// `#[cfg(test)]`, and deliberately NOT public. Each one's own documentation
116// says not to bounds-check against it, which is disqualifying for a stable
117// export: the names read like enforced limits, they sit next to the `MIN_`/
118// `MAX_` constants that really are enforced, and `MIN_DECODED_SALT_LEN` even
119// holds the same value as the real bound today. A caller who reaches for one
120// gets a limit the decoder does not apply. They stay here so
121// `decoded_mirrors_are_not_decoder_bounds` can keep pinning the gap.
122
123/// `ARGON2_MAX_DECODED_LANES`.
124///
125/// Mirrored from `encoding.h:22`, and **not a bound this crate enforces**. The
126/// C defines the macro there and then never reads it, in `encoding.c` or
127/// anywhere else in the tree, so `decode_string` here does not read it either.
128/// What actually bounds the `p=` field of a decoded PHC string is
129/// [`MAX_LANES`] (`0x00FF_FFFF`), applied by [`validate_inputs`] inside
130/// `decode_string`.
131///
132/// Do not use this constant to bounds-check decoded input: a well-formed
133/// string can carry a `p` far above 255 and will decode and verify. Measured
134/// against this crate, `p=300` round-trips through `hash_encoded` and
135/// `verify_encoded`:
136///
137/// ```text
138/// $argon2id$v=19$m=2400,t=1,p=300$c29tZXNhbHQ$tPLI8hre65Crk/uP5eIGCZzn3TQ7RzRoXIkGzt5jQoI
139/// ```
140///
141/// (`m=2400` because [`validate_inputs`] requires `m_cost >= 8 * lanes`, not
142/// because 255 played any part.) The `decoded_mirrors_are_not_decoder_bounds`
143/// test pins the gap between this value and the bound that is real.
144#[cfg(test)]
145const MAX_DECODED_LANES: u32 = 255;
146/// `ARGON2_MIN_DECODED_SALT_LEN`.
147///
148/// Mirrored from `encoding.h:23`, and unread for the same reason: the C
149/// defines it and never consults it, so `decode_string` here does not either.
150/// The salt of a decoded string is bounded by [`MIN_SALT_LENGTH`], applied by
151/// [`validate_inputs`].
152///
153/// The two happen to hold the same value (8) today, which is exactly what
154/// makes this constant easy to mistake for the enforced minimum. It is not the
155/// enforced minimum, and nothing ties the two together: they come from
156/// different headers (`encoding.h` and `argon2.h`), and if [`MIN_SALT_LENGTH`]
157/// ever moves the decoder moves with it while this value stays at 8. Check
158/// decoded salts against [`MIN_SALT_LENGTH`].
159#[cfg(test)]
160const MIN_DECODED_SALT_LEN: u32 = 8;
161/// `ARGON2_MIN_DECODED_OUT_LEN`.
162///
163/// Mirrored from `encoding.h:24`, and likewise never read by the C, so
164/// `decode_string` here does not read it either. The tag length of a decoded
165/// string is bounded by [`MIN_OUTLEN`], applied by [`validate_inputs`].
166///
167/// Do not use this constant to bounds-check decoded input: [`MIN_OUTLEN`] is
168/// 4, so a decoded tag can legitimately undershoot 12. Measured against this
169/// crate, an 8-byte tag round-trips through `hash_encoded` and
170/// `verify_encoded`:
171///
172/// ```text
173/// $argon2id$v=19$m=2400,t=1,p=1$c29tZXNhbHQ$kQGQLZpZJIk
174/// ```
175#[cfg(test)]
176const MIN_DECODED_OUT_LEN: u32 = 12;
177
178// ---------------------------------------------------------------------------
179// Algorithm
180// ---------------------------------------------------------------------------
181
182/// The Argon2 primitive type (`argon2_type`).
183///
184/// The numeric values matter: `initial_hash` hashes them, and `fill_segment`
185/// puts `instance->type` into `input_block.v[5]`.
186#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
187#[repr(u32)]
188pub enum Algorithm {
189 /// `Argon2_d` (0): data-dependent addressing.
190 Argon2d = 0,
191 /// `Argon2_i` (1): data-independent addressing.
192 Argon2i = 1,
193 /// `Argon2_id` (2): first half-pass independent, rest dependent. The default.
194 #[default]
195 Argon2id = 2,
196}
197
198impl Algorithm {
199 /// The `argon2_type` numeric value.
200 #[inline]
201 #[must_use]
202 pub const fn as_u32(self) -> u32 {
203 self as u32
204 }
205
206 /// Parse an `argon2_type` numeric value.
207 #[inline]
208 #[must_use]
209 pub const fn from_u32(value: u32) -> Option<Algorithm> {
210 match value {
211 0 => Some(Algorithm::Argon2d),
212 1 => Some(Algorithm::Argon2i),
213 2 => Some(Algorithm::Argon2id),
214 _ => None,
215 }
216 }
217
218 /// `argon2_type2string(type, 0)`: the lowercase name used in PHC strings.
219 ///
220 /// Note `"argon2i"` is a prefix of `"argon2id"`; the C decoder relies on
221 /// the *next* character failing to parse, and the Rust decoder must too.
222 #[inline]
223 #[must_use]
224 pub const fn as_str(self) -> &'static str {
225 match self {
226 Algorithm::Argon2d => "argon2d",
227 Algorithm::Argon2i => "argon2i",
228 Algorithm::Argon2id => "argon2id",
229 }
230 }
231
232 /// `argon2_type2string(type, 1)`: the capitalised name (used by genkat).
233 #[inline]
234 #[must_use]
235 pub const fn as_str_uppercase(self) -> &'static str {
236 match self {
237 Algorithm::Argon2d => "Argon2d",
238 Algorithm::Argon2i => "Argon2i",
239 Algorithm::Argon2id => "Argon2id",
240 }
241 }
242
243 /// All three variants, in `argon2_type` order.
244 pub const ALL: [Algorithm; 3] = [Algorithm::Argon2d, Algorithm::Argon2i, Algorithm::Argon2id];
245}
246
247// ---------------------------------------------------------------------------
248// Version
249// ---------------------------------------------------------------------------
250
251/// The Argon2 version (`argon2_version`).
252#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
253#[repr(u32)]
254pub enum Version {
255 /// `ARGON2_VERSION_10` (0x10). Blocks are always overwritten, never XORed.
256 V0x10 = 0x10,
257 /// `ARGON2_VERSION_13` (0x13) — `ARGON2_VERSION_NUMBER`, the default.
258 #[default]
259 V0x13 = 0x13,
260}
261
262impl Version {
263 /// `ARGON2_VERSION_NUMBER`.
264 pub const DEFAULT: Version = Version::V0x13;
265
266 /// Both variants, ascending.
267 pub const ALL: [Version; 2] = [Version::V0x10, Version::V0x13];
268
269 /// The `argon2_version` numeric value.
270 #[inline]
271 #[must_use]
272 pub const fn as_u32(self) -> u32 {
273 self as u32
274 }
275
276 /// Parse an `argon2_version` numeric value.
277 #[inline]
278 #[must_use]
279 pub const fn from_u32(value: u32) -> Option<Version> {
280 match value {
281 0x10 => Some(Version::V0x10),
282 0x13 => Some(Version::V0x13),
283 _ => None,
284 }
285 }
286}
287
288// ---------------------------------------------------------------------------
289// validate_inputs
290// ---------------------------------------------------------------------------
291
292/// `validate_inputs()` from `src/core.c`, in the exact same order.
293///
294/// The order is load-bearing: when several inputs are invalid, the C reference
295/// returns the error for whichever check runs first, and the differential tests
296/// compare error codes.
297///
298/// Checks the C performs that are omitted here, with the reason:
299///
300/// * `context == NULL` → `ARGON2_INCORRECT_PARAMETER`: no null contexts in Rust.
301/// * `out == NULL` → `ARGON2_OUTPUT_PTR_NULL`: no null slices in Rust.
302/// * the four `*_PTR_MISMATCH` checks: a Rust slice always has a valid pointer.
303/// * `ARGON2_MIN_PWD_LENGTH > pwdlen`, `ARGON2_MIN_AD_LENGTH > adlen`,
304/// `ARGON2_MIN_SECRET > secretlen`: those minima are all 0, so the checks can
305/// never fire (and would be tautological comparisons in Rust).
306/// * the two allocator-callback checks: this crate has no allocator callbacks.
307///
308/// Note the C computes `8 * context->lanes` in `uint32_t`, *before* `lanes` has
309/// been range-checked, so it can wrap. [`u32::wrapping_mul`] reproduces that:
310/// `lanes = 0xFFFF_FFFF` yields `MemoryTooLittle`, not `LanesTooMany`.
311///
312/// # Prefer [`Params::validate_for`]
313///
314/// This free function is the escape hatch, not the main path.
315/// [`Params::validate_for`] calls it with five of the nine arguments filled in
316/// from the receiver: the tag length (`out_len`, from [`Params::output_len`])
317/// and the four cost values (`m_cost`, `t_cost`, `lanes`, `threads`). It leaves
318/// the caller exactly the four buffer lengths, `pwd_len`, `salt_len`,
319/// `secret_len` and `ad_len`. Those five values come from a [`Params`] that a
320/// constructor already ran through this function, so they cannot drift from the
321/// costs the hash will actually run with, and `core` takes that route on every
322/// hash.
323///
324/// Reach for this function directly only when the C's exact check ordering is
325/// what is wanted, which is the one thing the `Params` route cannot give you:
326/// [`Params::new`] and [`Params::new_with_threads`] validate the cost parameters
327/// at construction time, so a caller who supplies both a bad `m_cost` and a
328/// short salt sees the `m_cost` error where the C reports
329/// `ARGON2_SALT_TOO_SHORT` (the divergence note on [`Params`] spells this out).
330/// `decode_string` is the in-crate example: it calls this function directly on
331/// the decoded fields and only builds its `Params` afterwards, so that a
332/// malformed PHC string yields the same error code `validate_inputs()`
333/// (`core.c:388-513`) yields in the C.
334///
335/// ```
336/// use argon2_rust::Error;
337/// use argon2_rust::params::{Params, validate_inputs};
338///
339/// let params = Params::new(19_456, 2, 1, 32)?;
340///
341/// // Four arguments. The tag length and the four costs come from `params`.
342/// assert_eq!(params.validate_for(8, 16, 0, 0), Ok(()));
343///
344/// // The same check spelled out. The five values `params` would have supplied
345/// // have to be repeated by hand and kept in step with it.
346/// assert_eq!(validate_inputs(32, 8, 16, 0, 0, 19_456, 2, 1, 1), Ok(()));
347///
348/// // `out_len` and `pwd_len` transposed, which is the pair `validate_for`
349/// // takes off the call site entirely. Both are `usize` and adjacent, so this
350/// // compiles, and there is no error to notice: the password length 8 is now
351/// // the tag length, 8 clears `MIN_OUTLEN` (4), and the call says `Ok(())`
352/// // while agreeing to a 64-bit tag.
353/// assert_eq!(validate_inputs(8, 32, 16, 0, 0, 19_456, 2, 1, 1), Ok(()));
354///
355/// // The method form cannot be told that. `out_len` is not one of its four
356/// // arguments; it comes from the `Params`, which holds it at 32.
357/// assert_eq!(params.output_len(), 32);
358/// assert_eq!(params.validate_for(32, 16, 0, 0), Ok(()));
359/// # Ok::<(), Error>(())
360/// ```
361// `MAX_TIME` is `u32::MAX`, and so is `MAX_MEMORY` on a 64-bit target, which
362// makes those two upper-bound checks tautologically false there. They are kept
363// verbatim so the check order matches the C exactly, and because `MAX_MEMORY` is
364// `0x20_0000` on a 32-bit target, where the check is real.
365#[allow(clippy::absurd_extreme_comparisons)]
366// Nine parameters, one per `argon2_context` field the C checks. Grouping them
367// would obscure the 1:1 correspondence with `validate_inputs()`.
368#[allow(clippy::too_many_arguments)]
369pub const fn validate_inputs(
370 out_len: usize,
371 pwd_len: usize,
372 salt_len: usize,
373 secret_len: usize,
374 ad_len: usize,
375 m_cost: u32,
376 t_cost: u32,
377 lanes: u32,
378 threads: u32,
379) -> Result<(), Error> {
380 // Validate output length.
381 if out_len < MIN_OUTLEN as usize {
382 return Err(Error::OutputTooShort);
383 }
384 if out_len > MAX_OUTLEN as usize {
385 return Err(Error::OutputTooLong);
386 }
387
388 // Validate password (required param).
389 if pwd_len > MAX_PWD_LENGTH as usize {
390 return Err(Error::PwdTooLong);
391 }
392
393 // Validate salt (required param). Note the C checks the length even when
394 // `salt == NULL`, so an empty salt is `SaltTooShort`, not a ptr mismatch.
395 if salt_len < MIN_SALT_LENGTH as usize {
396 return Err(Error::SaltTooShort);
397 }
398 if salt_len > MAX_SALT_LENGTH as usize {
399 return Err(Error::SaltTooLong);
400 }
401
402 // Validate secret (optional param).
403 if secret_len > MAX_SECRET as usize {
404 return Err(Error::SecretTooLong);
405 }
406
407 // Validate associated data (optional param).
408 if ad_len > MAX_AD_LENGTH as usize {
409 return Err(Error::AdTooLong);
410 }
411
412 // Validate memory cost. Three checks, in this order.
413 if m_cost < MIN_MEMORY {
414 return Err(Error::MemoryTooLittle);
415 }
416 if m_cost > MAX_MEMORY {
417 return Err(Error::MemoryTooMuch);
418 }
419 if m_cost < 8u32.wrapping_mul(lanes) {
420 return Err(Error::MemoryTooLittle);
421 }
422
423 // Validate time cost.
424 if t_cost < MIN_TIME {
425 return Err(Error::TimeTooSmall);
426 }
427 if t_cost > MAX_TIME {
428 return Err(Error::TimeTooLarge);
429 }
430
431 // Validate lanes.
432 if lanes < MIN_LANES {
433 return Err(Error::LanesTooFew);
434 }
435 if lanes > MAX_LANES {
436 return Err(Error::LanesTooMany);
437 }
438
439 // Validate threads.
440 if threads < MIN_THREADS {
441 return Err(Error::ThreadsTooFew);
442 }
443 if threads > MAX_THREADS {
444 return Err(Error::ThreadsTooMany);
445 }
446
447 Ok(())
448}
449
450// ---------------------------------------------------------------------------
451// Params
452// ---------------------------------------------------------------------------
453
454/// Validated Argon2 cost parameters.
455///
456/// Holds exactly the fields of `argon2_context` that are *not* byte buffers:
457/// `m_cost`, `t_cost`, `lanes`, `threads` and `outlen`. Password, salt, secret
458/// and associated data are passed per call.
459///
460/// A `Params` value can only be built through a constructor that runs
461/// [`validate_inputs`], so `lanes >= 1` always holds and the derived values
462/// below never divide by zero.
463///
464/// # Known divergence from the C reference
465///
466/// The constructors validate the cost parameters immediately, whereas the C
467/// checks salt length *before* `m_cost`. If a caller supplies both a bad
468/// `m_cost` and a short salt, this crate reports the `m_cost` error at
469/// `Params` construction time while the C reports `ARGON2_SALT_TOO_SHORT`.
470/// Call [`validate_inputs`] directly to reproduce the C ordering exactly.
471#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
472pub struct Params {
473 m_cost: u32,
474 t_cost: u32,
475 lanes: u32,
476 threads: u32,
477 output_len: u32,
478}
479
480impl Params {
481 /// Default memory cost in KiB (19 MiB), per the OWASP Argon2id guidance.
482 pub const DEFAULT_M_COST: u32 = 19456;
483 /// Default number of passes.
484 pub const DEFAULT_T_COST: u32 = 2;
485 /// Default degree of parallelism.
486 pub const DEFAULT_LANES: u32 = 1;
487 /// Default tag length in bytes.
488 pub const DEFAULT_OUTPUT_LEN: usize = 32;
489
490 /// Validate and build parameters, with `threads == lanes`.
491 ///
492 /// This matches `argon2_hash()`, which sets both `context.lanes` and
493 /// `context.threads` from its single `parallelism` argument.
494 ///
495 /// # Errors
496 ///
497 /// Any of the cost-parameter errors from [`validate_inputs`].
498 pub const fn new(
499 m_cost: u32,
500 t_cost: u32,
501 lanes: u32,
502 output_len: usize,
503 ) -> Result<Params, Error> {
504 Params::new_with_threads(m_cost, t_cost, lanes, lanes, output_len)
505 }
506
507 /// Validate and build parameters with an explicit thread count.
508 ///
509 /// `threads` is a pure performance knob: it does **not** affect the tag.
510 /// Only `lanes` does. The effective count is `min(threads, lanes)`, see
511 /// [`Params::effective_threads`].
512 ///
513 /// ```
514 /// use argon2_rust::{Algorithm, Argon2, Params, Version};
515 ///
516 /// // Four lanes of work, but never more than two OS threads to run them.
517 /// let budgeted = Params::new_with_threads(64, 1, 4, 2, 32)?;
518 /// assert_eq!((budgeted.lanes(), budgeted.threads()), (4, 2));
519 /// assert_eq!(budgeted.effective_threads(), 2);
520 ///
521 /// // Asking for more threads than lanes is legal, and the extra workers
522 /// // simply have no lane to claim.
523 /// let oversubscribed = Params::new_with_threads(64, 1, 2, 8, 32)?;
524 /// assert_eq!(oversubscribed.effective_threads(), 2);
525 ///
526 /// // `Params::new` is exactly this call with `threads == lanes`.
527 /// let full = Params::new(64, 1, 4, 32)?;
528 /// assert_eq!(full, Params::new_with_threads(64, 1, 4, 4, 32)?);
529 ///
530 /// // And the knob really is free of the tag: same `lanes`, same bytes,
531 /// // whichever thread budget produced them.
532 /// let two_workers = Argon2::new(Algorithm::Argon2id, Version::V0x13, budgeted);
533 /// let four_workers = Argon2::new(Algorithm::Argon2id, Version::V0x13, full);
534 /// assert_eq!(
535 /// two_workers.hash(b"password", b"somesalt")?,
536 /// four_workers.hash(b"password", b"somesalt")?,
537 /// );
538 /// # Ok::<(), argon2_rust::Error>(())
539 /// ```
540 ///
541 /// # Errors
542 ///
543 /// Any of the cost-parameter errors from [`validate_inputs`].
544 pub const fn new_with_threads(
545 m_cost: u32,
546 t_cost: u32,
547 lanes: u32,
548 threads: u32,
549 output_len: usize,
550 ) -> Result<Params, Error> {
551 // Feed placeholder lengths that always pass their own checks, so the
552 // *relative* order of the checks that do apply is exactly the C's.
553 match validate_inputs(
554 output_len,
555 0,
556 MIN_SALT_LENGTH as usize,
557 0,
558 0,
559 m_cost,
560 t_cost,
561 lanes,
562 threads,
563 ) {
564 Ok(()) => {}
565 Err(e) => return Err(e),
566 }
567 Ok(Params {
568 m_cost,
569 t_cost,
570 lanes,
571 threads,
572 // `output_len <= MAX_OUTLEN == u32::MAX` was just checked.
573 output_len: output_len as u32,
574 })
575 }
576
577 /// Run the full `validate_inputs()` sequence for a concrete call.
578 ///
579 /// `core` calls this on every hash so the salt/password/secret/ad checks
580 /// fire in the C's order.
581 ///
582 /// # Errors
583 ///
584 /// Any error from [`validate_inputs`].
585 pub const fn validate_for(
586 &self,
587 pwd_len: usize,
588 salt_len: usize,
589 secret_len: usize,
590 ad_len: usize,
591 ) -> Result<(), Error> {
592 validate_inputs(
593 self.output_len as usize,
594 pwd_len,
595 salt_len,
596 secret_len,
597 ad_len,
598 self.m_cost,
599 self.t_cost,
600 self.lanes,
601 self.threads,
602 )
603 }
604
605 /// Requested memory in KiB (`context.m_cost`).
606 #[inline]
607 #[must_use]
608 pub const fn m_cost(&self) -> u32 {
609 self.m_cost
610 }
611
612 /// Number of passes (`context.t_cost`, `instance.passes`).
613 #[inline]
614 #[must_use]
615 pub const fn t_cost(&self) -> u32 {
616 self.t_cost
617 }
618
619 /// Degree of parallelism (`context.lanes`). Affects the tag.
620 #[inline]
621 #[must_use]
622 pub const fn lanes(&self) -> u32 {
623 self.lanes
624 }
625
626 /// Requested worker threads (`context.threads`). Does not affect the tag.
627 #[inline]
628 #[must_use]
629 pub const fn threads(&self) -> u32 {
630 self.threads
631 }
632
633 /// Tag length in bytes (`context.outlen`).
634 #[inline]
635 #[must_use]
636 pub const fn output_len(&self) -> usize {
637 self.output_len as usize
638 }
639
640 /// `min(threads, lanes)`, as `argon2_ctx` computes it.
641 #[inline]
642 #[must_use]
643 pub const fn effective_threads(&self) -> u32 {
644 if self.threads > self.lanes {
645 self.lanes
646 } else {
647 self.threads
648 }
649 }
650
651 /// Step 2 of `argon2_ctx()`: align the memory size.
652 ///
653 /// ```text
654 /// memory_blocks = m_cost;
655 /// if (memory_blocks < 2 * SYNC_POINTS * lanes)
656 /// memory_blocks = 2 * SYNC_POINTS * lanes;
657 /// segment_length = memory_blocks / (lanes * SYNC_POINTS);
658 /// memory_blocks = segment_length * (lanes * SYNC_POINTS);
659 /// lane_length = segment_length * SYNC_POINTS;
660 /// ```
661 ///
662 /// Returns `(memory_blocks, segment_length, lane_length)`. No overflow is
663 /// possible: `lanes <= MAX_LANES` (`0xFF_FFFF`), so `lanes * SYNC_POINTS`
664 /// fits comfortably in `u32`, and `segment_length * lanes * SYNC_POINTS`
665 /// is bounded by the original `memory_blocks <= MAX_MEMORY`.
666 #[inline]
667 #[must_use]
668 pub const fn memory_layout(&self) -> (u32, u32, u32) {
669 let lanes_x_sync = self.lanes * SYNC_POINTS;
670 let min_blocks = 2 * SYNC_POINTS * self.lanes;
671
672 let mut memory_blocks = self.m_cost;
673 if memory_blocks < min_blocks {
674 memory_blocks = min_blocks;
675 }
676
677 let segment_length = memory_blocks / lanes_x_sync;
678 memory_blocks = segment_length * lanes_x_sync;
679 let lane_length = segment_length * SYNC_POINTS;
680
681 (memory_blocks, segment_length, lane_length)
682 }
683
684 /// Number of 1 KiB blocks the arena needs (`instance.memory_blocks`).
685 #[inline]
686 #[must_use]
687 pub const fn memory_blocks(&self) -> u32 {
688 self.memory_layout().0
689 }
690
691 /// Blocks per segment (`instance.segment_length`).
692 #[inline]
693 #[must_use]
694 pub const fn segment_length(&self) -> u32 {
695 self.memory_layout().1
696 }
697
698 /// Blocks per lane (`instance.lane_length` = `segment_length * SYNC_POINTS`).
699 #[inline]
700 #[must_use]
701 pub const fn lane_length(&self) -> u32 {
702 self.memory_layout().2
703 }
704}
705
706impl Default for Params {
707 /// `m_cost = 19456` KiB, `t_cost = 2`, `lanes = 1`, `output_len = 32`.
708 fn default() -> Params {
709 Params {
710 m_cost: Params::DEFAULT_M_COST,
711 t_cost: Params::DEFAULT_T_COST,
712 lanes: Params::DEFAULT_LANES,
713 threads: Params::DEFAULT_LANES,
714 output_len: Params::DEFAULT_OUTPUT_LEN as u32,
715 }
716 }
717}
718
719#[cfg(test)]
720mod tests {
721 use super::*;
722
723 #[test]
724 fn constants_match_the_c_preprocessor() {
725 // Printed by compiling include/argon2.h on aarch64-apple-darwin:
726 // MAX_MEMORY_BITS = 32, MAX_MEMORY = 4294967295, MIN_MEMORY = 8,
727 // MAX_OUTLEN = 4294967295, MAX_LANES = 16777215
728 assert_eq!(MIN_MEMORY, 8);
729 assert_eq!(MAX_LANES, 16_777_215);
730 assert_eq!(MAX_OUTLEN, 4_294_967_295);
731 if size_of::<*const u8>() == 8 {
732 assert_eq!(MAX_MEMORY_BITS, 32);
733 assert_eq!(MAX_MEMORY, 4_294_967_295);
734 }
735 assert_eq!(BLOCK_SIZE, 1024);
736 assert_eq!(QWORDS_IN_BLOCK, 128);
737 assert_eq!(OWORDS_IN_BLOCK, 64);
738 assert_eq!(HWORDS_IN_BLOCK, 32);
739 assert_eq!(BITS512_WORDS_IN_BLOCK, 16);
740 assert_eq!(PREHASH_SEED_LENGTH - PREHASH_DIGEST_LENGTH, 8);
741 }
742
743 #[test]
744 fn decoded_mirrors_are_not_decoder_bounds() {
745 // The three `encoding.h` mirrors are read by nobody: not by the C, and
746 // so not by `decode_string` here either. Each one's doc tells a caller
747 // not to bounds-check against it. This pins the gap that makes that
748 // advice true, so the prose cannot go stale.
749 //
750 // In `const` blocks so the checks run at compile time: every operand is
751 // a constant, so a violation is a build error rather than a red test,
752 // and clippy::assertions_on_constants stays quiet.
753
754 // `lanes` is bounded by `MAX_LANES` (16_777_215), roughly 65_000x this
755 // value. Measured: `$argon2id$v=19$m=2400,t=1,p=300$...` encodes and
756 // verifies, with `p` well past 255. A strict `<` is the point — if the
757 // two ever met, "this is not the bound" would be false.
758 const { assert!(MAX_DECODED_LANES < MAX_LANES) }
759
760 // The mirror sits ABOVE the enforced minimum (12 against 4), which is
761 // what lets a decoded tag legitimately undershoot it. Measured: an
762 // 8-byte tag round-trips.
763 const { assert!(MIN_DECODED_OUT_LEN > MIN_OUTLEN) }
764
765 // The salt mirror is the nastiest of the three because it agrees with
766 // the enforced bound today, which is exactly why it reads like the
767 // enforced bound. Pinned as equal on purpose: the day `MIN_SALT_LENGTH`
768 // moves, this fails and sends the next reader to the doc above that
769 // says "they happen to hold the same value (8) today" — prose that
770 // would otherwise quietly become wrong.
771 const { assert!(MIN_DECODED_SALT_LEN == MIN_SALT_LENGTH) }
772 }
773
774 /// Pins the two PHC strings the docs on `MAX_DECODED_LANES` and
775 /// `MIN_DECODED_OUT_LEN` quote as evidence.
776 ///
777 /// Each of those docs tells a caller not to bounds-check decoded input
778 /// against the constant, and each backs the advice with a measured string
779 /// that breaks the constant and round-trips anyway: `p=300` against a
780 /// documented 255, and an 8-byte tag against a documented 12. The strings
781 /// were pasted in from a run of this crate and nothing recomputed them, so
782 /// a change to `encode_string`, to the tag derivation, or to what
783 /// `Params::new` does with `m_cost` would leave the docs quoting output the
784 /// crate no longer produces, with no test failing. Reproduced here from the
785 /// parameters those docs state, byte for byte, and verified back through
786 /// `verify_encoded` so the word "round-trips" is pinned too.
787 #[test]
788 fn decoded_bound_docs_quote_strings_this_crate_still_produces() {
789 use crate::Argon2;
790
791 // `MAX_DECODED_LANES` is 255 and this is `p=300`. `m=2400` is forced by
792 // `validate_inputs`' `m_cost >= 8 * lanes` rule, not by 255.
793 let params = Params::new(2400, 1, 300, 32).unwrap();
794 let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
795 let encoded = argon2.hash_encoded(b"password", b"somesalt").unwrap();
796 assert_eq!(
797 encoded,
798 "$argon2id$v=19$m=2400,t=1,p=300$c29tZXNhbHQ$tPLI8hre65Crk/uP5eIGCZzn3TQ7RzRoXIkGzt5jQoI"
799 );
800 assert_eq!(
801 Argon2::verify_encoded(&encoded, b"password", Algorithm::Argon2id),
802 Ok(())
803 );
804
805 // `MIN_DECODED_OUT_LEN` is 12 and this tag is 8 bytes, which `MIN_OUTLEN`
806 // (4) allows. Same `m` and salt as above so the two strings differ only
807 // where the docs say they do.
808 let params = Params::new(2400, 1, 1, 8).unwrap();
809 let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
810 let encoded = argon2.hash_encoded(b"password", b"somesalt").unwrap();
811 assert_eq!(
812 encoded,
813 "$argon2id$v=19$m=2400,t=1,p=1$c29tZXNhbHQ$kQGQLZpZJIk"
814 );
815 assert_eq!(
816 Argon2::verify_encoded(&encoded, b"password", Algorithm::Argon2id),
817 Ok(())
818 );
819 }
820
821 #[test]
822 fn default_params_are_valid() {
823 let d = Params::default();
824 assert!(d.validate_for(0, 8, 0, 0).is_ok());
825 }
826
827 #[test]
828 fn validate_order_salt_before_m_cost() {
829 // Both are bad; the C checks salt first.
830 assert_eq!(
831 validate_inputs(32, 0, 0, 0, 0, 0, 1, 1, 1),
832 Err(Error::SaltTooShort)
833 );
834 }
835
836 #[test]
837 fn validate_order_out_len_first() {
838 assert_eq!(
839 validate_inputs(0, 0, 0, 0, 0, 0, 0, 0, 0),
840 Err(Error::OutputTooShort)
841 );
842 }
843
844 #[test]
845 fn m_cost_lanes_product_wraps_like_c() {
846 // 8 * 0xFFFF_FFFF wraps to 0xFFFF_FFF8, so any sane m_cost is "too
847 // little" and LanesTooMany never gets a chance to fire.
848 assert_eq!(
849 validate_inputs(32, 0, 8, 0, 0, 1 << 16, 1, 0xFFFF_FFFF, 1),
850 Err(Error::MemoryTooLittle)
851 );
852 // With lanes in range, the 8*lanes rule is the third memory check.
853 assert_eq!(
854 validate_inputs(32, 0, 8, 0, 0, 16, 1, 4, 4),
855 Err(Error::MemoryTooLittle)
856 );
857 assert_eq!(validate_inputs(32, 0, 8, 0, 0, 32, 1, 4, 4), Ok(()));
858 }
859
860 #[test]
861 fn lanes_zero_is_lanes_too_few() {
862 // 8 * 0 == 0, so the memory checks pass and LanesTooFew surfaces.
863 assert_eq!(
864 validate_inputs(32, 0, 8, 0, 0, 8, 1, 0, 1),
865 Err(Error::LanesTooFew)
866 );
867 }
868
869 #[test]
870 fn memory_layout_matches_argon2_ctx() {
871 // m_cost below the floor gets bumped to 2 * SYNC_POINTS * lanes.
872 let p = Params::new(8, 1, 1, 32).unwrap();
873 assert_eq!(p.memory_layout(), (8, 2, 8));
874
875 // 1 << 16 KiB, one lane: 65536 blocks, 16384 per segment.
876 let p = Params::new(1 << 16, 2, 1, 32).unwrap();
877 assert_eq!(p.memory_layout(), (65536, 16384, 65536));
878
879 // Four lanes: segment_length = 65536 / 16 = 4096, lane_length = 16384.
880 let p = Params::new(1 << 16, 2, 4, 32).unwrap();
881 assert_eq!(p.memory_layout(), (65536, 4096, 16384));
882
883 // Not a multiple of lanes * SYNC_POINTS: truncated down.
884 let p = Params::new(100, 1, 3, 32).unwrap();
885 let (blocks, seg, lane) = p.memory_layout();
886 assert_eq!(seg, 100 / 12);
887 assert_eq!(blocks, seg * 12);
888 assert_eq!(lane, seg * 4);
889 }
890
891 #[test]
892 fn effective_threads_is_min() {
893 let p = Params::new_with_threads(1 << 16, 1, 2, 8, 32).unwrap();
894 assert_eq!(p.threads(), 8);
895 assert_eq!(p.effective_threads(), 2);
896 }
897
898 #[test]
899 fn algorithm_and_version_round_trip() {
900 for a in Algorithm::ALL {
901 assert_eq!(Algorithm::from_u32(a.as_u32()), Some(a));
902 }
903 assert_eq!(Algorithm::from_u32(3), None);
904 assert_eq!(Algorithm::Argon2id.as_str(), "argon2id");
905 assert_eq!(Algorithm::Argon2i.as_str_uppercase(), "Argon2i");
906 for v in Version::ALL {
907 assert_eq!(Version::from_u32(v.as_u32()), Some(v));
908 }
909 assert_eq!(Version::from_u32(0x11), None);
910 assert_eq!(Version::default(), Version::V0x13);
911 assert_eq!(Algorithm::default(), Algorithm::Argon2id);
912 }
913}