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// Typed units
84// ---------------------------------------------------------------------------
85
86/// A memory cost, carried in kibibytes.
87///
88/// The unit is the point of this type. Argon2's `m_cost` is a count of 1 KiB
89/// blocks, so a bare `65536` at a call site could be read as bytes, KiB, MiB or
90/// blocks; `Memory::mib(64)` cannot.
91///
92/// No constructor validates or panics. The value is held as a `u64` and checked
93/// once, by [`ParamsBuilder::build`], which is the only place that knows the
94/// target's `MAX_MEMORY`.
95///
96/// ```
97/// use argon2_rust::params::Memory;
98///
99/// assert_eq!(Memory::mib(64), Memory::kib(65536));
100/// assert_eq!(Memory::gib(1), Memory::mib(1024));
101/// ```
102#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
103pub struct Memory(u64);
104
105impl Memory {
106 /// A cost in kibibytes, the unit Argon2's `m_cost` uses.
107 #[inline]
108 #[must_use]
109 pub const fn kib(kib: u64) -> Memory {
110 Memory(kib)
111 }
112
113 /// A cost in mebibytes.
114 ///
115 /// Saturating, not wrapping: `mib(u64::MAX)` yields `u64::MAX` KiB rather
116 /// than panicking in a debug build. Any saturated value is far above
117 /// [`MAX_MEMORY`] and becomes [`Error::MemoryTooMuch`] at `build()`.
118 #[inline]
119 #[must_use]
120 pub const fn mib(mib: u64) -> Memory {
121 Memory(mib.saturating_mul(1024))
122 }
123
124 /// A cost in gibibytes. Saturating, for the reason given on [`Memory::mib`].
125 #[inline]
126 #[must_use]
127 pub const fn gib(gib: u64) -> Memory {
128 Memory(gib.saturating_mul(1024 * 1024))
129 }
130
131 /// The cost in kibibytes.
132 #[inline]
133 #[must_use]
134 pub const fn as_kib(self) -> u64 {
135 self.0
136 }
137}
138
139/// A tag length, carried in bytes.
140///
141/// There is deliberately no `bits()` constructor: a bit count that is not a
142/// whole number of bytes would be the only new failure mode in this API, and
143/// `TagLen::bytes(32)` already names the unit at the call site. RFC 9106's
144/// "256-bit tag" is written `TagLen::bytes(32)`.
145///
146/// Like [`Memory`], this validates nothing; [`ParamsBuilder::build`] does.
147///
148/// ```
149/// use argon2_rust::params::TagLen;
150///
151/// assert_eq!(TagLen::bytes(32).as_bytes(), 32);
152/// ```
153#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
154pub struct TagLen(u64);
155
156impl TagLen {
157 /// A tag length in bytes.
158 #[inline]
159 #[must_use]
160 pub const fn bytes(bytes: u64) -> TagLen {
161 TagLen(bytes)
162 }
163
164 /// The length in bytes.
165 #[inline]
166 #[must_use]
167 pub const fn as_bytes(self) -> u64 {
168 self.0
169 }
170}
171
172// ---------------------------------------------------------------------------
173// Internal constants from src/core.h
174// ---------------------------------------------------------------------------
175
176/// `ARGON2_BLOCK_SIZE`: memory block size in bytes.
177pub const BLOCK_SIZE: usize = 1024;
178/// `ARGON2_QWORDS_IN_BLOCK`: 64-bit words per block.
179pub const QWORDS_IN_BLOCK: usize = BLOCK_SIZE / 8;
180/// `ARGON2_OWORDS_IN_BLOCK`: 128-bit lanes per block (SSE2).
181pub const OWORDS_IN_BLOCK: usize = BLOCK_SIZE / 16;
182/// `ARGON2_HWORDS_IN_BLOCK`: 256-bit lanes per block (AVX2).
183pub const HWORDS_IN_BLOCK: usize = BLOCK_SIZE / 32;
184/// `ARGON2_512BIT_WORDS_IN_BLOCK`: 512-bit lanes per block (AVX-512).
185pub const BITS512_WORDS_IN_BLOCK: usize = BLOCK_SIZE / 64;
186
187/// `ARGON2_ADDRESSES_IN_BLOCK`: pseudo-random values one address block holds.
188pub const ADDRESSES_IN_BLOCK: usize = 128;
189
190/// `ARGON2_PREHASH_DIGEST_LENGTH`: length of `H0`.
191pub const PREHASH_DIGEST_LENGTH: usize = 64;
192/// `ARGON2_PREHASH_SEED_LENGTH`: `H0` plus the 4-byte block index and 4-byte lane index.
193pub const PREHASH_SEED_LENGTH: usize = 72;
194
195// ---------------------------------------------------------------------------
196// Limits from src/encoding.h
197// ---------------------------------------------------------------------------
198//
199// Mirrored for completeness, and unused — the C defines all three in
200// `encoding.h:22-24` and then never reads them, so `decode_string` here does
201// not either. Keeping them (rather than dropping them) is what makes the
202// header-for-header correspondence with the C checkable; do not add a use for
203// them without checking the C grew one first.
204//
205// `#[cfg(test)]`, and deliberately NOT public. Each one's own documentation
206// says not to bounds-check against it, which is disqualifying for a stable
207// export: the names read like enforced limits, they sit next to the `MIN_`/
208// `MAX_` constants that really are enforced, and `MIN_DECODED_SALT_LEN` even
209// holds the same value as the real bound today. A caller who reaches for one
210// gets a limit the decoder does not apply. They stay here so
211// `decoded_mirrors_are_not_decoder_bounds` can keep pinning the gap.
212
213/// `ARGON2_MAX_DECODED_LANES`.
214///
215/// Mirrored from `encoding.h:22`, and **not a bound this crate enforces**. The
216/// C defines the macro there and then never reads it, in `encoding.c` or
217/// anywhere else in the tree, so `decode_string` here does not read it either.
218/// What actually bounds the `p=` field of a decoded PHC string is
219/// [`MAX_LANES`] (`0x00FF_FFFF`), applied by [`validate_inputs`] inside
220/// `decode_string`.
221///
222/// Do not use this constant to bounds-check decoded input: a well-formed
223/// string can carry a `p` far above 255 and will decode and verify. Measured
224/// against this crate, `p=300` round-trips through `hash_encoded` and
225/// `verify_encoded`:
226///
227/// ```text
228/// $argon2id$v=19$m=2400,t=1,p=300$c29tZXNhbHQ$tPLI8hre65Crk/uP5eIGCZzn3TQ7RzRoXIkGzt5jQoI
229/// ```
230///
231/// (`m=2400` because [`validate_inputs`] requires `m_cost >= 8 * lanes`, not
232/// because 255 played any part.) The `decoded_mirrors_are_not_decoder_bounds`
233/// test pins the gap between this value and the bound that is real.
234#[cfg(test)]
235const MAX_DECODED_LANES: u32 = 255;
236/// `ARGON2_MIN_DECODED_SALT_LEN`.
237///
238/// Mirrored from `encoding.h:23`, and unread for the same reason: the C
239/// defines it and never consults it, so `decode_string` here does not either.
240/// The salt of a decoded string is bounded by [`MIN_SALT_LENGTH`], applied by
241/// [`validate_inputs`].
242///
243/// The two happen to hold the same value (8) today, which is exactly what
244/// makes this constant easy to mistake for the enforced minimum. It is not the
245/// enforced minimum, and nothing ties the two together: they come from
246/// different headers (`encoding.h` and `argon2.h`), and if [`MIN_SALT_LENGTH`]
247/// ever moves the decoder moves with it while this value stays at 8. Check
248/// decoded salts against [`MIN_SALT_LENGTH`].
249#[cfg(test)]
250const MIN_DECODED_SALT_LEN: u32 = 8;
251/// `ARGON2_MIN_DECODED_OUT_LEN`.
252///
253/// Mirrored from `encoding.h:24`, and likewise never read by the C, so
254/// `decode_string` here does not read it either. The tag length of a decoded
255/// string is bounded by [`MIN_OUTLEN`], applied by [`validate_inputs`].
256///
257/// Do not use this constant to bounds-check decoded input: [`MIN_OUTLEN`] is
258/// 4, so a decoded tag can legitimately undershoot 12. Measured against this
259/// crate, an 8-byte tag round-trips through `hash_encoded` and
260/// `verify_encoded`:
261///
262/// ```text
263/// $argon2id$v=19$m=2400,t=1,p=1$c29tZXNhbHQ$kQGQLZpZJIk
264/// ```
265#[cfg(test)]
266const MIN_DECODED_OUT_LEN: u32 = 12;
267
268// ---------------------------------------------------------------------------
269// Algorithm
270// ---------------------------------------------------------------------------
271
272/// The Argon2 primitive type (`argon2_type`).
273///
274/// The numeric values matter: `initial_hash` hashes them, and `fill_segment`
275/// puts `instance->type` into `input_block.v[5]`.
276#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
277#[repr(u32)]
278pub enum Algorithm {
279 /// `Argon2_d` (0): data-dependent addressing.
280 Argon2d = 0,
281 /// `Argon2_i` (1): data-independent addressing.
282 Argon2i = 1,
283 /// `Argon2_id` (2): first half-pass independent, rest dependent. The default.
284 #[default]
285 Argon2id = 2,
286}
287
288impl Algorithm {
289 /// The `argon2_type` numeric value.
290 #[inline]
291 #[must_use]
292 pub const fn as_u32(self) -> u32 {
293 self as u32
294 }
295
296 /// Parse an `argon2_type` numeric value.
297 #[inline]
298 #[must_use]
299 pub const fn from_u32(value: u32) -> Option<Algorithm> {
300 match value {
301 0 => Some(Algorithm::Argon2d),
302 1 => Some(Algorithm::Argon2i),
303 2 => Some(Algorithm::Argon2id),
304 _ => None,
305 }
306 }
307
308 /// `argon2_type2string(type, 0)`: the lowercase name used in PHC strings.
309 ///
310 /// Note `"argon2i"` is a prefix of `"argon2id"`; the C decoder relies on
311 /// the *next* character failing to parse, and the Rust decoder must too.
312 #[inline]
313 #[must_use]
314 pub const fn as_str(self) -> &'static str {
315 match self {
316 Algorithm::Argon2d => "argon2d",
317 Algorithm::Argon2i => "argon2i",
318 Algorithm::Argon2id => "argon2id",
319 }
320 }
321
322 /// `argon2_type2string(type, 1)`: the capitalised name (used by genkat).
323 #[inline]
324 #[must_use]
325 pub const fn as_str_uppercase(self) -> &'static str {
326 match self {
327 Algorithm::Argon2d => "Argon2d",
328 Algorithm::Argon2i => "Argon2i",
329 Algorithm::Argon2id => "Argon2id",
330 }
331 }
332
333 /// All three variants, in `argon2_type` order.
334 pub const ALL: [Algorithm; 3] = [Algorithm::Argon2d, Algorithm::Argon2i, Algorithm::Argon2id];
335}
336
337// ---------------------------------------------------------------------------
338// Version
339// ---------------------------------------------------------------------------
340
341/// The Argon2 version (`argon2_version`).
342#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
343#[repr(u32)]
344pub enum Version {
345 /// `ARGON2_VERSION_10` (0x10). Blocks are always overwritten, never XORed.
346 V0x10 = 0x10,
347 /// `ARGON2_VERSION_13` (0x13) — `ARGON2_VERSION_NUMBER`, the default.
348 #[default]
349 V0x13 = 0x13,
350}
351
352impl Version {
353 /// `ARGON2_VERSION_NUMBER`.
354 pub const DEFAULT: Version = Version::V0x13;
355
356 /// Both variants, ascending.
357 pub const ALL: [Version; 2] = [Version::V0x10, Version::V0x13];
358
359 /// The `argon2_version` numeric value.
360 #[inline]
361 #[must_use]
362 pub const fn as_u32(self) -> u32 {
363 self as u32
364 }
365
366 /// Parse an `argon2_version` numeric value.
367 #[inline]
368 #[must_use]
369 pub const fn from_u32(value: u32) -> Option<Version> {
370 match value {
371 0x10 => Some(Version::V0x10),
372 0x13 => Some(Version::V0x13),
373 _ => None,
374 }
375 }
376}
377
378// ---------------------------------------------------------------------------
379// validate_inputs
380// ---------------------------------------------------------------------------
381
382/// `validate_inputs()` from `src/core.c`, in the exact same order.
383///
384/// The order is load-bearing: when several inputs are invalid, the C reference
385/// returns the error for whichever check runs first, and the differential tests
386/// compare error codes.
387///
388/// Checks the C performs that are omitted here, with the reason:
389///
390/// * `context == NULL` → `ARGON2_INCORRECT_PARAMETER`: no null contexts in Rust.
391/// * `out == NULL` → `ARGON2_OUTPUT_PTR_NULL`: no null slices in Rust.
392/// * the four `*_PTR_MISMATCH` checks: a Rust slice always has a valid pointer.
393/// * `ARGON2_MIN_PWD_LENGTH > pwdlen`, `ARGON2_MIN_AD_LENGTH > adlen`,
394/// `ARGON2_MIN_SECRET > secretlen`: those minima are all 0, so the checks can
395/// never fire (and would be tautological comparisons in Rust).
396/// * the two allocator-callback checks: this crate has no allocator callbacks.
397///
398/// Note the C computes `8 * context->lanes` in `uint32_t`, *before* `lanes` has
399/// been range-checked, so it can wrap. [`u32::wrapping_mul`] reproduces that:
400/// `lanes = 0xFFFF_FFFF` yields `MemoryTooLittle`, not `LanesTooMany`.
401///
402/// # Prefer [`Params::validate_for`]
403///
404/// This free function is the escape hatch, not the main path.
405/// [`Params::validate_for`] calls it with five of the nine arguments filled in
406/// from the receiver: the tag length (`out_len`, from [`Params::tag_len_bytes`])
407/// and the four cost values (`m_cost`, `t_cost`, `lanes`, `threads`). It leaves
408/// the caller exactly the four buffer lengths, `pwd_len`, `salt_len`,
409/// `secret_len` and `ad_len`. Those five values come from a [`Params`] that
410/// [`ParamsBuilder::build`] already ran through this function, so they cannot
411/// drift from the costs the hash will actually run with, and `core` takes that
412/// route on every hash.
413///
414/// Reach for this function directly only when the C's exact check ordering is
415/// what is wanted, which is the one thing the `Params` route cannot give you:
416/// [`ParamsBuilder::build`] validates the cost parameters at construction time,
417/// so a caller who supplies both a bad `m_cost` and a short salt sees the
418/// `m_cost` error where the C reports `ARGON2_SALT_TOO_SHORT` (the divergence
419/// note on [`Params`] spells this out).
420/// `decode_string` is the in-crate example: it calls this function directly on
421/// the decoded fields and only builds its `Params` afterwards, so that a
422/// malformed PHC string yields the same error code `validate_inputs()`
423/// (`core.c:388-513`) yields in the C.
424///
425/// ```
426/// use argon2_rust::Error;
427/// use argon2_rust::params::{Memory, Params, validate_inputs};
428///
429/// let params = Params::builder().memory(Memory::kib(19_456)).passes(2).build()?;
430///
431/// // Four arguments. The tag length and the four costs come from `params`.
432/// assert_eq!(params.validate_for(8, 16, 0, 0), Ok(()));
433///
434/// // The same check spelled out. The five values `params` would have supplied
435/// // have to be repeated by hand and kept in step with it.
436/// assert_eq!(validate_inputs(32, 8, 16, 0, 0, 19_456, 2, 1, 1), Ok(()));
437///
438/// // `out_len` and `pwd_len` transposed, which is the pair `validate_for`
439/// // takes off the call site entirely. Both are `usize` and adjacent, so this
440/// // compiles, and there is no error to notice: the password length 8 is now
441/// // the tag length, 8 clears `MIN_OUTLEN` (4), and the call says `Ok(())`
442/// // while agreeing to a 64-bit tag.
443/// assert_eq!(validate_inputs(8, 32, 16, 0, 0, 19_456, 2, 1, 1), Ok(()));
444///
445/// // The method form cannot be told that. `out_len` is not one of its four
446/// // arguments; it comes from the `Params`, which holds it at 32.
447/// assert_eq!(params.tag_len_bytes(), 32);
448/// assert_eq!(params.validate_for(32, 16, 0, 0), Ok(()));
449/// # Ok::<(), Error>(())
450/// ```
451// `MAX_TIME` is `u32::MAX`, and so is `MAX_MEMORY` on a 64-bit target, which
452// makes those two upper-bound checks tautologically false there. They are kept
453// verbatim so the check order matches the C exactly, and because `MAX_MEMORY` is
454// `0x20_0000` on a 32-bit target, where the check is real.
455#[allow(clippy::absurd_extreme_comparisons)]
456// Nine parameters, one per `argon2_context` field the C checks. Grouping them
457// would obscure the 1:1 correspondence with `validate_inputs()`.
458#[allow(clippy::too_many_arguments)]
459pub const fn validate_inputs(
460 out_len: usize,
461 pwd_len: usize,
462 salt_len: usize,
463 secret_len: usize,
464 ad_len: usize,
465 m_cost: u32,
466 t_cost: u32,
467 lanes: u32,
468 threads: u32,
469) -> Result<(), Error> {
470 // Validate output length.
471 if out_len < MIN_OUTLEN as usize {
472 return Err(Error::OutputTooShort);
473 }
474 if out_len > MAX_OUTLEN as usize {
475 return Err(Error::OutputTooLong);
476 }
477
478 // Validate password (required param).
479 if pwd_len > MAX_PWD_LENGTH as usize {
480 return Err(Error::PwdTooLong);
481 }
482
483 // Validate salt (required param). Note the C checks the length even when
484 // `salt == NULL`, so an empty salt is `SaltTooShort`, not a ptr mismatch.
485 if salt_len < MIN_SALT_LENGTH as usize {
486 return Err(Error::SaltTooShort);
487 }
488 if salt_len > MAX_SALT_LENGTH as usize {
489 return Err(Error::SaltTooLong);
490 }
491
492 // Validate secret (optional param).
493 if secret_len > MAX_SECRET as usize {
494 return Err(Error::SecretTooLong);
495 }
496
497 // Validate associated data (optional param).
498 if ad_len > MAX_AD_LENGTH as usize {
499 return Err(Error::AdTooLong);
500 }
501
502 // Validate memory cost. Three checks, in this order.
503 if m_cost < MIN_MEMORY {
504 return Err(Error::MemoryTooLittle);
505 }
506 if m_cost > MAX_MEMORY {
507 return Err(Error::MemoryTooMuch);
508 }
509 if m_cost < 8u32.wrapping_mul(lanes) {
510 return Err(Error::MemoryTooLittle);
511 }
512
513 // Validate time cost.
514 if t_cost < MIN_TIME {
515 return Err(Error::TimeTooSmall);
516 }
517 if t_cost > MAX_TIME {
518 return Err(Error::TimeTooLarge);
519 }
520
521 // Validate lanes.
522 if lanes < MIN_LANES {
523 return Err(Error::LanesTooFew);
524 }
525 if lanes > MAX_LANES {
526 return Err(Error::LanesTooMany);
527 }
528
529 // Validate threads.
530 if threads < MIN_THREADS {
531 return Err(Error::ThreadsTooFew);
532 }
533 if threads > MAX_THREADS {
534 return Err(Error::ThreadsTooMany);
535 }
536
537 Ok(())
538}
539
540// ---------------------------------------------------------------------------
541// Params
542// ---------------------------------------------------------------------------
543
544/// Validated Argon2 cost parameters.
545///
546/// Holds exactly the fields of `argon2_context` that are *not* byte buffers:
547/// `m_cost`, `t_cost`, `lanes`, `threads` and `outlen`. Password, salt, secret
548/// and associated data are passed per call.
549///
550/// There is no public field and no public constructor: every `Params` comes out
551/// of [`ParamsBuilder::build`], which runs [`validate_inputs`], or out of a
552/// preset ([`Params::DEFAULT`], [`Params::OWASP`],
553/// [`Params::RFC9106_HIGH_MEMORY`], [`Params::RFC9106_LOW_MEMORY`]) that
554/// `build`'s `const` twin already ran. So `lanes >= 1` always holds and the
555/// derived values below never divide by zero.
556///
557/// Start from [`Params::builder`], or from [`Params::to_builder`] to adjust an
558/// existing value.
559///
560/// # Known divergence from the C reference
561///
562/// [`ParamsBuilder::build`] validates the cost parameters immediately, whereas
563/// the C checks salt length *before* `m_cost`. If a caller supplies both a bad
564/// `m_cost` and a short salt, this crate reports the `m_cost` error at
565/// `Params` construction time while the C reports `ARGON2_SALT_TOO_SHORT`.
566/// Call [`validate_inputs`] directly to reproduce the C ordering exactly.
567#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
568pub struct Params {
569 m_cost: u32,
570 t_cost: u32,
571 lanes: u32,
572 threads: u32,
573 output_len: u32,
574}
575
576/// Builder for [`Params`].
577///
578/// Every setter is a `const fn` taking `self` by value, so a `Params` can be
579/// built in a `const` item — see [`ParamsBuilder::build_or_panic`]. The builder
580/// starts from [`ParamsBuilder::DEFAULT`], so each setter is optional.
581///
582/// ```
583/// use argon2_rust::{Params, params::{Memory, TagLen}};
584///
585/// let params = Params::builder()
586/// .memory(Memory::mib(64))
587/// .passes(3)
588/// .lanes(4)
589/// .tag_len(TagLen::bytes(32))
590/// .build()?;
591/// assert_eq!(params.memory(), Memory::mib(64));
592/// # Ok::<(), argon2_rust::Error>(())
593/// ```
594#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
595pub struct ParamsBuilder {
596 memory: Memory,
597 passes: u32,
598 lanes: u32,
599 /// `None` means "track `lanes`", which is what `argon2_hash()` does: it
600 /// sets both `context.lanes` and `context.threads` from one argument.
601 /// Storing the choice rather than eagerly copying `lanes` is what makes
602 /// `.threads(2).lanes(4)` and `.lanes(4).threads(2)` agree.
603 threads: Option<u32>,
604 tag_len: TagLen,
605}
606
607impl ParamsBuilder {
608 /// The starting point: OWASP's Argon2id profile, 19 MiB, two passes, one
609 /// lane, a 32-byte tag.
610 ///
611 /// These are literals rather than a copy of [`Params::DEFAULT`]'s fields
612 /// on purpose. `Params::DEFAULT` is built *by* this builder, so reading it
613 /// here would be a cyclic `const`.
614 pub const DEFAULT: ParamsBuilder = ParamsBuilder {
615 memory: Memory::kib(19456),
616 passes: 2,
617 lanes: 1,
618 threads: None,
619 tag_len: TagLen::bytes(32),
620 };
621
622 /// Set the memory cost.
623 #[inline]
624 #[must_use]
625 pub const fn memory(mut self, memory: Memory) -> ParamsBuilder {
626 self.memory = memory;
627 self
628 }
629
630 /// Set the number of passes (`t_cost` in the C, `t=` in a PHC string).
631 #[inline]
632 #[must_use]
633 pub const fn passes(mut self, passes: u32) -> ParamsBuilder {
634 self.passes = passes;
635 self
636 }
637
638 /// Set the degree of parallelism (`p=` in a PHC string).
639 ///
640 /// This one feeds the tag. Changing it changes the hash.
641 #[inline]
642 #[must_use]
643 pub const fn lanes(mut self, lanes: u32) -> ParamsBuilder {
644 self.lanes = lanes;
645 self
646 }
647
648 /// Set the worker-thread budget.
649 ///
650 /// A pure performance knob: it does **not** affect the tag. Only `lanes`
651 /// does. Left unset it tracks [`ParamsBuilder::lanes`], which is what
652 /// `argon2_hash()` does — it sets both `context.lanes` and
653 /// `context.threads` from its single `parallelism` argument. The effective
654 /// count is `min(threads, lanes)`, see [`Params::effective_threads`].
655 ///
656 /// ```
657 /// use argon2_rust::{Algorithm, Argon2, Params, Version, params::Memory};
658 ///
659 /// // Four lanes of work, but never more than two OS threads to run them.
660 /// let budgeted = Params::builder()
661 /// .memory(Memory::kib(64))
662 /// .passes(1)
663 /// .lanes(4)
664 /// .threads(2)
665 /// .build()?;
666 /// assert_eq!((budgeted.lanes(), budgeted.threads()), (4, 2));
667 /// assert_eq!(budgeted.effective_threads(), 2);
668 ///
669 /// // Asking for more threads than lanes is legal, and the extra workers
670 /// // simply have no lane to claim.
671 /// let oversubscribed = Params::builder()
672 /// .memory(Memory::kib(64))
673 /// .passes(1)
674 /// .lanes(2)
675 /// .threads(8)
676 /// .build()?;
677 /// assert_eq!(oversubscribed.effective_threads(), 2);
678 ///
679 /// // Leaving it unset is exactly `threads == lanes`.
680 /// let full = Params::builder()
681 /// .memory(Memory::kib(64))
682 /// .passes(1)
683 /// .lanes(4)
684 /// .build()?;
685 /// assert_eq!(full, budgeted.to_builder().threads(4).build()?);
686 /// assert_eq!(full.threads(), 4);
687 ///
688 /// // And the knob really is free of the tag: same `lanes`, same bytes,
689 /// // whichever thread budget produced them.
690 /// let two_workers = Argon2::new(Algorithm::Argon2id, Version::V0x13, budgeted);
691 /// let four_workers = Argon2::new(Algorithm::Argon2id, Version::V0x13, full);
692 /// assert_eq!(
693 /// two_workers.hash(b"password", b"somesalt")?,
694 /// four_workers.hash(b"password", b"somesalt")?,
695 /// );
696 /// # Ok::<(), argon2_rust::Error>(())
697 /// ```
698 #[inline]
699 #[must_use]
700 pub const fn threads(mut self, threads: u32) -> ParamsBuilder {
701 self.threads = Some(threads);
702 self
703 }
704
705 /// Set the tag length.
706 #[inline]
707 #[must_use]
708 pub const fn tag_len(mut self, tag_len: TagLen) -> ParamsBuilder {
709 self.tag_len = tag_len;
710 self
711 }
712
713 /// Validate and produce [`Params`].
714 ///
715 /// # Errors
716 ///
717 /// Any of the cost-parameter errors from [`validate_inputs`], plus
718 /// [`Error::OutputTooLong`] and [`Error::MemoryTooMuch`] for values too
719 /// large for this target at all.
720 pub const fn build(self) -> Result<Params, Error> {
721 // `Memory` and `TagLen` hold `u64`; `validate_inputs` takes a `u32`
722 // memory cost and a `usize` output length. Range-check BEFORE
723 // narrowing: on a 32-bit target `(1u64 << 40) as usize` is 0, which
724 // would turn OutputTooLong into OutputTooShort.
725 //
726 // The order here is the C's. `validate_inputs` checks BOTH `out_len`
727 // bounds before it looks at `m_cost`, so both are checked here too —
728 // pre-checking only the upper bound would report MemoryTooMuch for a
729 // 3-byte tag combined with an over-large memory cost, where the C
730 // reports OutputTooShort. That combination is reachable from a crafted
731 // PHC string, whose tag length and `m=` are both attacker-chosen.
732 let bytes = self.tag_len.as_bytes();
733 if bytes > MAX_OUTLEN as u64 {
734 return Err(Error::OutputTooLong);
735 }
736 if bytes < MIN_OUTLEN as u64 {
737 return Err(Error::OutputTooShort);
738 }
739 let kib = self.memory.as_kib();
740 if kib > MAX_MEMORY as u64 {
741 return Err(Error::MemoryTooMuch);
742 }
743
744 let threads = match self.threads {
745 Some(threads) => threads,
746 None => self.lanes,
747 };
748
749 // Placeholder lengths that always pass their own checks, so the
750 // *relative* order of the checks that do apply is exactly the C's.
751 match validate_inputs(
752 bytes as usize,
753 0,
754 MIN_SALT_LENGTH as usize,
755 0,
756 0,
757 kib as u32,
758 self.passes,
759 self.lanes,
760 threads,
761 ) {
762 Ok(()) => {}
763 Err(e) => return Err(e),
764 }
765
766 Ok(Params {
767 m_cost: kib as u32,
768 t_cost: self.passes,
769 lanes: self.lanes,
770 threads,
771 output_len: bytes as u32,
772 })
773 }
774
775 /// Validate and produce [`Params`], panicking on invalid parameters.
776 ///
777 /// This exists for `const` items, where a panic is a compile error:
778 ///
779 /// ```
780 /// use argon2_rust::{Params, params::Memory};
781 ///
782 /// const LOGIN: Params = Params::builder()
783 /// .memory(Memory::mib(64))
784 /// .passes(3)
785 /// .build_or_panic();
786 /// assert_eq!(LOGIN.passes(), 3);
787 /// ```
788 ///
789 /// # Panics
790 ///
791 /// If the parameters are invalid. Use [`ParamsBuilder::build`] anywhere a
792 /// runtime error is the right answer — it is the normal way in, and it is
793 /// why no fallible path in this crate panics.
794 #[must_use]
795 pub const fn build_or_panic(self) -> Params {
796 match self.build() {
797 Ok(params) => params,
798 Err(_) => panic!("invalid Argon2 parameters"),
799 }
800 }
801}
802
803impl Default for ParamsBuilder {
804 fn default() -> ParamsBuilder {
805 ParamsBuilder::DEFAULT
806 }
807}
808
809impl Params {
810 /// The recommended default: OWASP's Argon2id profile.
811 ///
812 /// 19 MiB, two passes, one lane, a 32-byte tag. Equal to [`Params::OWASP`]
813 /// and to `Params::default()`.
814 pub const DEFAULT: Params = ParamsBuilder::DEFAULT.build_or_panic();
815
816 /// OWASP's Argon2id profile: 19 MiB, `t=2`, `p=1`, 32-byte tag.
817 ///
818 /// The same value as [`Params::DEFAULT`], under the name that says where
819 /// the numbers come from.
820 pub const OWASP: Params = Params::DEFAULT;
821
822 /// RFC 9106 §4's first recommendation: 2 GiB, `t=1`, `p=4`, 32-byte tag.
823 ///
824 /// On a 32-bit target 2 GiB is exactly [`MAX_MEMORY`], so this constant
825 /// still compiles there — but the arena will fail to allocate inside a
826 /// 4 GiB address space. Prefer [`Params::RFC9106_LOW_MEMORY`] there.
827 pub const RFC9106_HIGH_MEMORY: Params = ParamsBuilder::DEFAULT
828 .memory(Memory::gib(2))
829 .passes(1)
830 .lanes(4)
831 .build_or_panic();
832
833 /// RFC 9106 §4's second recommendation, for memory-constrained systems:
834 /// 64 MiB, `t=3`, `p=4`, 32-byte tag.
835 pub const RFC9106_LOW_MEMORY: Params = ParamsBuilder::DEFAULT
836 .memory(Memory::mib(64))
837 .passes(3)
838 .lanes(4)
839 .build_or_panic();
840
841 /// Start building, from [`ParamsBuilder::DEFAULT`].
842 #[inline]
843 #[must_use]
844 pub const fn builder() -> ParamsBuilder {
845 ParamsBuilder::DEFAULT
846 }
847
848 /// Reopen these parameters for adjustment.
849 ///
850 /// ```
851 /// use argon2_rust::Params;
852 ///
853 /// let narrow = Params::RFC9106_LOW_MEMORY.to_builder().lanes(1).build()?;
854 /// assert_eq!(narrow.lanes(), 1);
855 /// # Ok::<(), argon2_rust::Error>(())
856 /// ```
857 #[inline]
858 #[must_use]
859 pub const fn to_builder(self) -> ParamsBuilder {
860 ParamsBuilder {
861 memory: Memory::kib(self.m_cost as u64),
862 passes: self.t_cost,
863 lanes: self.lanes,
864 // `Some`, not `None`: a round trip must preserve an explicit
865 // thread budget that differs from `lanes`.
866 threads: Some(self.threads),
867 tag_len: TagLen::bytes(self.output_len as u64),
868 }
869 }
870
871 /// The memory cost.
872 #[inline]
873 #[must_use]
874 pub const fn memory(&self) -> Memory {
875 Memory::kib(self.m_cost as u64)
876 }
877
878 /// The memory cost in kibibytes (`context.m_cost`, `m=` in a PHC string).
879 ///
880 /// A `u32`, losslessly: `build()` rejected anything wider.
881 #[inline]
882 #[must_use]
883 pub const fn memory_kib(&self) -> u32 {
884 self.m_cost
885 }
886
887 /// Number of passes (`context.t_cost`, `instance.passes`, `t=`).
888 #[inline]
889 #[must_use]
890 pub const fn passes(&self) -> u32 {
891 self.t_cost
892 }
893
894 /// The tag length.
895 #[inline]
896 #[must_use]
897 pub const fn tag_len(&self) -> TagLen {
898 TagLen::bytes(self.output_len as u64)
899 }
900
901 /// The tag length in bytes (`context.outlen`).
902 ///
903 /// A `usize`, losslessly: `build()` rejected anything wider.
904 #[inline]
905 #[must_use]
906 pub const fn tag_len_bytes(&self) -> usize {
907 self.output_len as usize
908 }
909
910 /// Run the full `validate_inputs()` sequence for a concrete call.
911 ///
912 /// `core` calls this on every hash so the salt/password/secret/ad checks
913 /// fire in the C's order.
914 ///
915 /// # Errors
916 ///
917 /// Any error from [`validate_inputs`].
918 pub const fn validate_for(
919 &self,
920 pwd_len: usize,
921 salt_len: usize,
922 secret_len: usize,
923 ad_len: usize,
924 ) -> Result<(), Error> {
925 validate_inputs(
926 self.output_len as usize,
927 pwd_len,
928 salt_len,
929 secret_len,
930 ad_len,
931 self.m_cost,
932 self.t_cost,
933 self.lanes,
934 self.threads,
935 )
936 }
937
938 /// Degree of parallelism (`context.lanes`). Affects the tag.
939 #[inline]
940 #[must_use]
941 pub const fn lanes(&self) -> u32 {
942 self.lanes
943 }
944
945 /// Requested worker threads (`context.threads`). Does not affect the tag.
946 #[inline]
947 #[must_use]
948 pub const fn threads(&self) -> u32 {
949 self.threads
950 }
951
952 /// `min(threads, lanes)`, as `argon2_ctx` computes it.
953 #[inline]
954 #[must_use]
955 pub const fn effective_threads(&self) -> u32 {
956 if self.threads > self.lanes {
957 self.lanes
958 } else {
959 self.threads
960 }
961 }
962
963 /// Step 2 of `argon2_ctx()`: align the memory size.
964 ///
965 /// ```text
966 /// memory_blocks = m_cost;
967 /// if (memory_blocks < 2 * SYNC_POINTS * lanes)
968 /// memory_blocks = 2 * SYNC_POINTS * lanes;
969 /// segment_length = memory_blocks / (lanes * SYNC_POINTS);
970 /// memory_blocks = segment_length * (lanes * SYNC_POINTS);
971 /// lane_length = segment_length * SYNC_POINTS;
972 /// ```
973 ///
974 /// Returns `(memory_blocks, segment_length, lane_length)`. No overflow is
975 /// possible: `lanes <= MAX_LANES` (`0xFF_FFFF`), so `lanes * SYNC_POINTS`
976 /// fits comfortably in `u32`, and `segment_length * lanes * SYNC_POINTS`
977 /// is bounded by the original `memory_blocks <= MAX_MEMORY`.
978 #[inline]
979 #[must_use]
980 pub const fn memory_layout(&self) -> (u32, u32, u32) {
981 let lanes_x_sync = self.lanes * SYNC_POINTS;
982 let min_blocks = 2 * SYNC_POINTS * self.lanes;
983
984 let mut memory_blocks = self.m_cost;
985 if memory_blocks < min_blocks {
986 memory_blocks = min_blocks;
987 }
988
989 let segment_length = memory_blocks / lanes_x_sync;
990 memory_blocks = segment_length * lanes_x_sync;
991 let lane_length = segment_length * SYNC_POINTS;
992
993 (memory_blocks, segment_length, lane_length)
994 }
995
996 /// Number of 1 KiB blocks the arena needs (`instance.memory_blocks`).
997 #[inline]
998 #[must_use]
999 pub const fn memory_blocks(&self) -> u32 {
1000 self.memory_layout().0
1001 }
1002
1003 /// Blocks per segment (`instance.segment_length`).
1004 #[inline]
1005 #[must_use]
1006 pub const fn segment_length(&self) -> u32 {
1007 self.memory_layout().1
1008 }
1009
1010 /// Blocks per lane (`instance.lane_length` = `segment_length * SYNC_POINTS`).
1011 #[inline]
1012 #[must_use]
1013 pub const fn lane_length(&self) -> u32 {
1014 self.memory_layout().2
1015 }
1016}
1017
1018impl Default for Params {
1019 /// [`Params::DEFAULT`]: OWASP's profile, 19 MiB, `t=2`, `p=1`, 32-byte tag.
1020 fn default() -> Params {
1021 Params::DEFAULT
1022 }
1023}
1024
1025#[cfg(test)]
1026mod tests {
1027 use super::*;
1028
1029 #[test]
1030 fn constants_match_the_c_preprocessor() {
1031 // Printed by compiling include/argon2.h on aarch64-apple-darwin:
1032 // MAX_MEMORY_BITS = 32, MAX_MEMORY = 4294967295, MIN_MEMORY = 8,
1033 // MAX_OUTLEN = 4294967295, MAX_LANES = 16777215
1034 assert_eq!(MIN_MEMORY, 8);
1035 assert_eq!(MAX_LANES, 16_777_215);
1036 assert_eq!(MAX_OUTLEN, 4_294_967_295);
1037 if size_of::<*const u8>() == 8 {
1038 assert_eq!(MAX_MEMORY_BITS, 32);
1039 assert_eq!(MAX_MEMORY, 4_294_967_295);
1040 }
1041 assert_eq!(BLOCK_SIZE, 1024);
1042 assert_eq!(QWORDS_IN_BLOCK, 128);
1043 assert_eq!(OWORDS_IN_BLOCK, 64);
1044 assert_eq!(HWORDS_IN_BLOCK, 32);
1045 assert_eq!(BITS512_WORDS_IN_BLOCK, 16);
1046 assert_eq!(PREHASH_SEED_LENGTH - PREHASH_DIGEST_LENGTH, 8);
1047 }
1048
1049 #[test]
1050 fn decoded_mirrors_are_not_decoder_bounds() {
1051 // The three `encoding.h` mirrors are read by nobody: not by the C, and
1052 // so not by `decode_string` here either. Each one's doc tells a caller
1053 // not to bounds-check against it. This pins the gap that makes that
1054 // advice true, so the prose cannot go stale.
1055 //
1056 // In `const` blocks so the checks run at compile time: every operand is
1057 // a constant, so a violation is a build error rather than a red test,
1058 // and clippy::assertions_on_constants stays quiet.
1059
1060 // `lanes` is bounded by `MAX_LANES` (16_777_215), roughly 65_000x this
1061 // value. Measured: `$argon2id$v=19$m=2400,t=1,p=300$...` encodes and
1062 // verifies, with `p` well past 255. A strict `<` is the point — if the
1063 // two ever met, "this is not the bound" would be false.
1064 const { assert!(MAX_DECODED_LANES < MAX_LANES) }
1065
1066 // The mirror sits ABOVE the enforced minimum (12 against 4), which is
1067 // what lets a decoded tag legitimately undershoot it. Measured: an
1068 // 8-byte tag round-trips.
1069 const { assert!(MIN_DECODED_OUT_LEN > MIN_OUTLEN) }
1070
1071 // The salt mirror is the nastiest of the three because it agrees with
1072 // the enforced bound today, which is exactly why it reads like the
1073 // enforced bound. Pinned as equal on purpose: the day `MIN_SALT_LENGTH`
1074 // moves, this fails and sends the next reader to the doc above that
1075 // says "they happen to hold the same value (8) today" — prose that
1076 // would otherwise quietly become wrong.
1077 const { assert!(MIN_DECODED_SALT_LEN == MIN_SALT_LENGTH) }
1078 }
1079
1080 /// Pins the two PHC strings the docs on `MAX_DECODED_LANES` and
1081 /// `MIN_DECODED_OUT_LEN` quote as evidence.
1082 ///
1083 /// Each of those docs tells a caller not to bounds-check decoded input
1084 /// against the constant, and each backs the advice with a measured string
1085 /// that breaks the constant and round-trips anyway: `p=300` against a
1086 /// documented 255, and an 8-byte tag against a documented 12. The strings
1087 /// were pasted in from a run of this crate and nothing recomputed them, so
1088 /// a change to `encode_string`, to the tag derivation, or to what
1089 /// `ParamsBuilder::build` does with `m_cost` would leave the docs quoting
1090 /// output the crate no longer produces, with no test failing. Reproduced
1091 /// here from the parameters those docs state, byte for byte, and verified
1092 /// back through `verify_encoded` so the word "round-trips" is pinned too.
1093 #[test]
1094 fn decoded_bound_docs_quote_strings_this_crate_still_produces() {
1095 use crate::Argon2;
1096
1097 // `MAX_DECODED_LANES` is 255 and this is `p=300`. `m=2400` is forced by
1098 // `validate_inputs`' `m_cost >= 8 * lanes` rule, not by 255.
1099 let params = Params::builder()
1100 .memory(Memory::kib(2400))
1101 .passes(1)
1102 .lanes(300)
1103 .tag_len(TagLen::bytes(32))
1104 .build()
1105 .unwrap();
1106 let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
1107 let encoded = argon2.hash_encoded(b"password", b"somesalt").unwrap();
1108 assert_eq!(
1109 encoded,
1110 "$argon2id$v=19$m=2400,t=1,p=300$c29tZXNhbHQ$tPLI8hre65Crk/uP5eIGCZzn3TQ7RzRoXIkGzt5jQoI"
1111 );
1112 assert_eq!(
1113 Argon2::verify_encoded(&encoded, b"password", Algorithm::Argon2id),
1114 Ok(())
1115 );
1116
1117 // `MIN_DECODED_OUT_LEN` is 12 and this tag is 8 bytes, which `MIN_OUTLEN`
1118 // (4) allows. Same `m` and salt as above so the two strings differ only
1119 // where the docs say they do.
1120 let params = Params::builder()
1121 .memory(Memory::kib(2400))
1122 .passes(1)
1123 .lanes(1)
1124 .tag_len(TagLen::bytes(8))
1125 .build()
1126 .unwrap();
1127 let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
1128 let encoded = argon2.hash_encoded(b"password", b"somesalt").unwrap();
1129 assert_eq!(
1130 encoded,
1131 "$argon2id$v=19$m=2400,t=1,p=1$c29tZXNhbHQ$kQGQLZpZJIk"
1132 );
1133 assert_eq!(
1134 Argon2::verify_encoded(&encoded, b"password", Algorithm::Argon2id),
1135 Ok(())
1136 );
1137 }
1138
1139 #[test]
1140 fn default_params_are_valid() {
1141 let d = Params::default();
1142 assert!(d.validate_for(0, 8, 0, 0).is_ok());
1143 }
1144
1145 #[test]
1146 fn validate_order_salt_before_m_cost() {
1147 // Both are bad; the C checks salt first.
1148 assert_eq!(
1149 validate_inputs(32, 0, 0, 0, 0, 0, 1, 1, 1),
1150 Err(Error::SaltTooShort)
1151 );
1152 }
1153
1154 #[test]
1155 fn validate_order_out_len_first() {
1156 assert_eq!(
1157 validate_inputs(0, 0, 0, 0, 0, 0, 0, 0, 0),
1158 Err(Error::OutputTooShort)
1159 );
1160 }
1161
1162 #[test]
1163 fn m_cost_lanes_product_wraps_like_c() {
1164 // 8 * 0xFFFF_FFFF wraps to 0xFFFF_FFF8, so any sane m_cost is "too
1165 // little" and LanesTooMany never gets a chance to fire.
1166 assert_eq!(
1167 validate_inputs(32, 0, 8, 0, 0, 1 << 16, 1, 0xFFFF_FFFF, 1),
1168 Err(Error::MemoryTooLittle)
1169 );
1170 // With lanes in range, the 8*lanes rule is the third memory check.
1171 assert_eq!(
1172 validate_inputs(32, 0, 8, 0, 0, 16, 1, 4, 4),
1173 Err(Error::MemoryTooLittle)
1174 );
1175 assert_eq!(validate_inputs(32, 0, 8, 0, 0, 32, 1, 4, 4), Ok(()));
1176 }
1177
1178 #[test]
1179 fn lanes_zero_is_lanes_too_few() {
1180 // 8 * 0 == 0, so the memory checks pass and LanesTooFew surfaces.
1181 assert_eq!(
1182 validate_inputs(32, 0, 8, 0, 0, 8, 1, 0, 1),
1183 Err(Error::LanesTooFew)
1184 );
1185 }
1186
1187 #[test]
1188 fn memory_layout_matches_argon2_ctx() {
1189 // m_cost below the floor gets bumped to 2 * SYNC_POINTS * lanes.
1190 let p = Params::builder()
1191 .memory(Memory::kib(8))
1192 .passes(1)
1193 .lanes(1)
1194 .tag_len(TagLen::bytes(32))
1195 .build()
1196 .unwrap();
1197 assert_eq!(p.memory_layout(), (8, 2, 8));
1198
1199 // 1 << 16 KiB, one lane: 65536 blocks, 16384 per segment.
1200 let p = Params::builder()
1201 .memory(Memory::kib(1 << 16))
1202 .passes(2)
1203 .lanes(1)
1204 .tag_len(TagLen::bytes(32))
1205 .build()
1206 .unwrap();
1207 assert_eq!(p.memory_layout(), (65536, 16384, 65536));
1208
1209 // Four lanes: segment_length = 65536 / 16 = 4096, lane_length = 16384.
1210 let p = Params::builder()
1211 .memory(Memory::kib(1 << 16))
1212 .passes(2)
1213 .lanes(4)
1214 .tag_len(TagLen::bytes(32))
1215 .build()
1216 .unwrap();
1217 assert_eq!(p.memory_layout(), (65536, 4096, 16384));
1218
1219 // Not a multiple of lanes * SYNC_POINTS: truncated down.
1220 let p = Params::builder()
1221 .memory(Memory::kib(100))
1222 .passes(1)
1223 .lanes(3)
1224 .tag_len(TagLen::bytes(32))
1225 .build()
1226 .unwrap();
1227 let (blocks, seg, lane) = p.memory_layout();
1228 assert_eq!(seg, 100 / 12);
1229 assert_eq!(blocks, seg * 12);
1230 assert_eq!(lane, seg * 4);
1231 }
1232
1233 #[test]
1234 fn effective_threads_is_min() {
1235 let p = Params::builder()
1236 .memory(Memory::kib(1 << 16))
1237 .passes(1)
1238 .lanes(2)
1239 .threads(8)
1240 .tag_len(TagLen::bytes(32))
1241 .build()
1242 .unwrap();
1243 assert_eq!(p.threads(), 8);
1244 assert_eq!(p.effective_threads(), 2);
1245 }
1246
1247 #[test]
1248 fn memory_units_convert() {
1249 assert_eq!(Memory::kib(65536), Memory::mib(64));
1250 assert_eq!(Memory::mib(1024), Memory::gib(1));
1251 assert_eq!(Memory::gib(2).as_kib(), 2 * 1024 * 1024);
1252 assert_eq!(Memory::kib(19456).as_kib(), 19456);
1253 }
1254
1255 /// A plain `*` would panic here in a debug build. These constructors promise
1256 /// not to, so an absurd request saturates and is rejected later, by `build()`.
1257 #[test]
1258 fn memory_saturates_instead_of_overflowing() {
1259 assert_eq!(Memory::mib(u64::MAX).as_kib(), u64::MAX);
1260 assert_eq!(Memory::gib(u64::MAX).as_kib(), u64::MAX);
1261 assert_eq!(Memory::gib(u64::MAX / 1024).as_kib(), u64::MAX);
1262 }
1263
1264 #[test]
1265 fn memory_orders_by_size() {
1266 assert!(Memory::mib(64) > Memory::kib(19456));
1267 assert!(Memory::gib(1) > Memory::mib(64));
1268 }
1269
1270 #[test]
1271 fn tag_len_carries_bytes() {
1272 assert_eq!(TagLen::bytes(32).as_bytes(), 32);
1273 assert!(TagLen::bytes(64) > TagLen::bytes(32));
1274 }
1275
1276 /// Both types must be usable in a `const` item, or the builder cannot be.
1277 #[test]
1278 fn units_are_const() {
1279 const M: Memory = Memory::mib(64);
1280 const T: TagLen = TagLen::bytes(32);
1281 assert_eq!(M.as_kib(), 65536);
1282 assert_eq!(T.as_bytes(), 32);
1283 }
1284
1285 /// A `const` item, not a `let`. This is the whole reason the setters take
1286 /// `self` by value; if constness regresses, this stops compiling.
1287 const CONST_BUILT: Params = Params::builder()
1288 .memory(Memory::mib(64))
1289 .passes(3)
1290 .lanes(4)
1291 .build_or_panic();
1292
1293 #[test]
1294 fn builder_builds_in_a_const_item() {
1295 assert_eq!(CONST_BUILT.memory_kib(), 65536);
1296 assert_eq!(CONST_BUILT.passes(), 3);
1297 assert_eq!(CONST_BUILT.lanes(), 4);
1298 assert_eq!(CONST_BUILT.threads(), 4);
1299 assert_eq!(CONST_BUILT.tag_len_bytes(), 32);
1300 }
1301
1302 #[test]
1303 fn threads_defaults_to_lanes_but_an_explicit_value_survives() {
1304 let implicit = Params::builder().lanes(4).build().unwrap();
1305 assert_eq!((implicit.lanes(), implicit.threads()), (4, 4));
1306
1307 // Order must not matter: setting lanes after threads may not clobber it.
1308 let explicit = Params::builder().threads(2).lanes(4).build().unwrap();
1309 assert_eq!((explicit.lanes(), explicit.threads()), (4, 2));
1310 assert_eq!(explicit.effective_threads(), 2);
1311 }
1312
1313 #[test]
1314 fn typed_and_raw_accessors_agree() {
1315 let p = Params::builder()
1316 .memory(Memory::mib(64))
1317 .tag_len(TagLen::bytes(64))
1318 .build()
1319 .unwrap();
1320 assert_eq!(p.memory(), Memory::mib(64));
1321 assert_eq!(p.memory().as_kib(), u64::from(p.memory_kib()));
1322 assert_eq!(p.tag_len(), TagLen::bytes(64));
1323 assert_eq!(p.tag_len().as_bytes() as usize, p.tag_len_bytes());
1324 }
1325
1326 #[test]
1327 fn presets_hold_their_documented_numbers() {
1328 assert_eq!(Params::OWASP, Params::DEFAULT);
1329 assert_eq!(Params::DEFAULT, Params::default());
1330
1331 assert_eq!(Params::OWASP.memory_kib(), 19456);
1332 assert_eq!((Params::OWASP.passes(), Params::OWASP.lanes()), (2, 1));
1333 assert_eq!(Params::OWASP.tag_len_bytes(), 32);
1334
1335 assert_eq!(Params::RFC9106_HIGH_MEMORY.memory(), Memory::gib(2));
1336 assert_eq!(
1337 (
1338 Params::RFC9106_HIGH_MEMORY.passes(),
1339 Params::RFC9106_HIGH_MEMORY.lanes()
1340 ),
1341 (1, 4)
1342 );
1343
1344 assert_eq!(Params::RFC9106_LOW_MEMORY.memory(), Memory::mib(64));
1345 assert_eq!(
1346 (
1347 Params::RFC9106_LOW_MEMORY.passes(),
1348 Params::RFC9106_LOW_MEMORY.lanes()
1349 ),
1350 (3, 4)
1351 );
1352 }
1353
1354 #[test]
1355 fn to_builder_round_trips_every_preset() {
1356 for preset in [
1357 Params::OWASP,
1358 Params::RFC9106_HIGH_MEMORY,
1359 Params::RFC9106_LOW_MEMORY,
1360 ] {
1361 assert_eq!(preset.to_builder().build(), Ok(preset));
1362 }
1363 }
1364
1365 #[test]
1366 fn a_preset_can_be_adjusted() {
1367 let narrow = Params::RFC9106_LOW_MEMORY
1368 .to_builder()
1369 .lanes(1)
1370 .build()
1371 .unwrap();
1372 assert_eq!(narrow.memory(), Memory::mib(64));
1373 assert_eq!(narrow.passes(), 3);
1374 assert_eq!(narrow.lanes(), 1);
1375 }
1376
1377 #[test]
1378 fn build_rejects_every_out_of_range_value() {
1379 let b = Params::builder();
1380 assert_eq!(
1381 b.memory(Memory::gib(9999)).build(),
1382 Err(Error::MemoryTooMuch)
1383 );
1384 assert_eq!(
1385 b.memory(Memory::kib(4)).build(),
1386 Err(Error::MemoryTooLittle)
1387 );
1388 assert_eq!(
1389 b.tag_len(TagLen::bytes(3)).build(),
1390 Err(Error::OutputTooShort)
1391 );
1392 assert_eq!(
1393 b.tag_len(TagLen::bytes(1 << 40)).build(),
1394 Err(Error::OutputTooLong)
1395 );
1396 assert_eq!(b.passes(0).build(), Err(Error::TimeTooSmall));
1397 assert_eq!(b.lanes(0).build(), Err(Error::LanesTooFew));
1398 assert_eq!(b.threads(0).build(), Err(Error::ThreadsTooFew));
1399 }
1400
1401 /// `validate_inputs` checks BOTH `out_len` bounds before `m_cost`: its
1402 /// "Validate output length" block runs before its "Validate memory cost"
1403 /// block. `build()`'s own pre-narrowing checks must keep that order, so a
1404 /// caller who gets both wrong sees the error the C would have reported.
1405 /// Both directions are pinned: checking only the upper bound first is the
1406 /// bug this test exists to catch.
1407 #[test]
1408 fn tag_len_is_checked_before_memory_like_the_c() {
1409 let too_long = Params::builder()
1410 .memory(Memory::gib(9999))
1411 .tag_len(TagLen::bytes(1 << 40))
1412 .build();
1413 assert_eq!(too_long, Err(Error::OutputTooLong));
1414
1415 // The case that a two-check implementation gets wrong: the tag is too
1416 // SHORT, so only `validate_inputs` would catch it — but the memory
1417 // pre-check would already have returned MemoryTooMuch. Reachable from a
1418 // crafted PHC string with a 3-byte tag and a large `m=`.
1419 let too_short = Params::builder()
1420 .memory(Memory::kib(MAX_MEMORY as u64 + 1))
1421 .tag_len(TagLen::bytes(3))
1422 .build();
1423 assert_eq!(too_short, Err(Error::OutputTooShort));
1424 }
1425
1426 /// A saturated `Memory` must be rejected, not truncated into a legal `u32`.
1427 #[test]
1428 fn a_saturated_memory_is_rejected() {
1429 assert_eq!(
1430 Params::builder().memory(Memory::gib(u64::MAX)).build(),
1431 Err(Error::MemoryTooMuch)
1432 );
1433 }
1434
1435 /// The narrowing guard in `build()`, pinned with the one value that makes a
1436 /// missing guard report the *wrong error* instead of no error.
1437 ///
1438 /// `Memory` holds a `u64` and `validate_inputs` takes a `u32`, so `build()`
1439 /// must range-check before it narrows. 8 TiB is chosen for exactly one
1440 /// reason: `(1u64 << 33) as u32` is **0**, and 0 is below `MIN_MEMORY`, so a
1441 /// guard that ran after the narrowing would answer `MemoryTooLittle` for an
1442 /// over-large request.
1443 ///
1444 /// What this protects is a direct builder input, not the PHC decoder:
1445 /// `decode_string` parses `m=` with `decimal_u32`, so no string can hand
1446 /// `build()` a memory value wider than `u32::MAX` KiB. A caller can, because
1447 /// `Memory::kib` takes a `u64` and `Memory::mib`/`Memory::gib` saturate into
1448 /// one — and answering `MemoryTooLittle` to a request for 8 TiB would be
1449 /// actively misleading.
1450 ///
1451 /// This is a different failure mode from the neighbours above, which is why
1452 /// it earns its own case rather than folding into them: `gib(9999)` and
1453 /// `gib(u64::MAX)` narrow to values *inside* the legal range on a 64-bit
1454 /// target, so a missing guard makes those two return `Ok`. `Memory::gib(8192)`
1455 /// is the same number if you prefer that spelling, but `kib(1u64 << 33)` is
1456 /// the one that shows the low 32 bits are zero, which is the whole point.
1457 #[test]
1458 fn memory_is_range_checked_before_it_is_narrowed() {
1459 assert_eq!(
1460 Params::builder().memory(Memory::kib(1u64 << 33)).build(),
1461 Err(Error::MemoryTooMuch)
1462 );
1463 // The claim about the low 32 bits, asserted rather than trusted.
1464 assert_eq!((1u64 << 33) as u32, 0);
1465 assert!(0 < MIN_MEMORY);
1466 }
1467
1468 #[test]
1469 fn algorithm_and_version_round_trip() {
1470 for a in Algorithm::ALL {
1471 assert_eq!(Algorithm::from_u32(a.as_u32()), Some(a));
1472 }
1473 assert_eq!(Algorithm::from_u32(3), None);
1474 assert_eq!(Algorithm::Argon2id.as_str(), "argon2id");
1475 assert_eq!(Algorithm::Argon2i.as_str_uppercase(), "Argon2i");
1476 for v in Version::ALL {
1477 assert_eq!(Version::from_u32(v.as_u32()), Some(v));
1478 }
1479 assert_eq!(Version::from_u32(0x11), None);
1480 assert_eq!(Version::default(), Version::V0x13);
1481 assert_eq!(Algorithm::default(), Algorithm::Argon2id);
1482 }
1483}