Skip to main content

argon2_rust/
core.rs

1//! The Argon2 driver: pre-hashing, the fill loop, finalisation, and the public
2//! [`Argon2`] entry points.
3//!
4//! Ported line by line from `phc-winner-argon2/src/core.c` (`initial_hash`,
5//! `fill_first_blocks`, `initialize`, `index_alpha`, `fill_memory_blocks`,
6//! `fill_memory_blocks_st`, `fill_memory_blocks_mt`, `finalize`) and
7//! `phc-winner-argon2/src/argon2.c` (`argon2_ctx`, `argon2_hash`,
8//! `argon2_verify`, `argon2_verify_ctx`, `argon2_compare`).
9
10use alloc::string::String;
11use alloc::vec::Vec;
12
13use crate::blake2b::{Blake2b, blake2b_long};
14use crate::block::{Block, Instance, Position};
15use crate::error::Error;
16use crate::fill_block::{Backend, FillSegmentFn};
17use crate::memory::{Arena, Workspace, clear_internal_memory, clear_internal_memory_u64};
18use crate::params::{
19    Algorithm, BLOCK_SIZE, MAX_PWD_LENGTH, PREHASH_DIGEST_LENGTH, PREHASH_SEED_LENGTH, Params,
20    SYNC_POINTS, Version,
21};
22#[cfg(test)]
23use crate::params::{Memory, TagLen};
24
25/// The KAT trace hook: `internal_kat(instance, pass)` from `src/genkat.c`.
26///
27/// Invoked after every pass with `(pass_index, whole_arena)`, at a point where
28/// every helper is parked at the pass boundary and cannot touch the arena.
29/// `tests/kat.rs` uses it to dump the arena the way `genkat` does.
30pub type PassTrace<'a> = &'a mut dyn FnMut(u32, &[Block]);
31
32// Structural regression hook for the zeroization boundary: stable hashing must
33// never ask `hash_in_arena` to copy H0 out of the blockhash it already wipes.
34// Thread-local keeps parallel libtest cases from charging one another.
35#[cfg(all(test, feature = "std"))]
36std::thread_local! {
37    static H0_COPY_COUNT: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
38}
39
40// ---------------------------------------------------------------------------
41// index_alpha  (contract: called by every fill_block backend)
42// ---------------------------------------------------------------------------
43
44/// `index_alpha()` from `src/core.c`: the absolute index of the reference block.
45///
46/// (contract) The `fill_block` backends call this once per block.
47///
48/// # The two traps
49///
50/// **1. Deliberate wrapping subtraction.** The C writes
51///
52/// ```c
53/// reference_area_size = position->slice * segment_length
54///                     + ((position->index == 0) ? (-1) : 0);
55/// ```
56///
57/// `-1` added to a `uint32_t` wraps, so when `slice * segment_length` is 0 the
58/// result is `0xFFFFFFFF`, not a negative number. The same shape appears in the
59/// `pass > 0` branch:
60///
61/// ```c
62/// reference_area_size = lane_length - segment_length
63///                     + ((position->index == 0) ? (-1) : 0);
64/// ```
65///
66/// In Rust these **must** be [`u32::wrapping_sub`] / [`u32::wrapping_add`].
67/// A plain `-` panics in debug and silently differs in release. This is the
68/// single most common way an Argon2 port breaks, and it only shows up for
69/// lane-crossing references at `index == 0` — which means the official
70/// single-lane vectors will not catch it. The `p > 1` vectors and KATs will.
71///
72/// **2. Exact integer widths in the position mapping.** All of this is `u64`:
73///
74/// ```text
75/// let mut rel = pseudo_rand as u64;          // the LOW u32 of the pseudo-random value
76/// rel = (rel * rel) >> 32;
77/// rel = (ras as u64) - 1 - (((ras as u64) * rel) >> 32);
78/// abs = ((start_position as u64 + rel) % lane_length as u64) as u32;
79/// ```
80///
81/// with one refinement the summary above glosses over and the C source settles:
82/// `reference_area_size - 1` is evaluated in **`uint32_t`** (both operands are
83/// 32-bit; the `int` literal converts to `unsigned int`) and only *then* widened
84/// for the outer subtraction. The two readings differ exactly when
85/// `reference_area_size == 0`: 32-bit-first gives `0x0000_0000_FFFF_FFFF`,
86/// 64-bit-first gives `0xFFFF_FFFF_FFFF_FFFF`. This port does it the C way.
87///
88/// # Starting position
89///
90/// ```text
91/// start_position = 0;
92/// if pass != 0 {
93///     start_position = if slice == SYNC_POINTS - 1 { 0 }
94///                      else { (slice + 1) * segment_length };
95/// }
96/// ```
97#[must_use]
98pub fn index_alpha(
99    instance: &Instance,
100    position: &Position,
101    pseudo_rand: u32,
102    same_lane: bool,
103) -> u32 {
104    // The C ends with `% instance->lane_length`. For any instance built from
105    // validated `Params`, `lane_length == segment_length * SYNC_POINTS >= 8`.
106    // This guard exists only so a hand-built degenerate `Instance` cannot turn
107    // that `%` into a division-by-zero panic; the crate must never panic.
108    if instance.lane_length == 0 {
109        return 0;
110    }
111
112    let reference_area_size: u32 = if position.pass == 0 {
113        // First pass.
114        if position.slice == 0 {
115            // core.c:210-211 `reference_area_size = position->index - 1;`
116            // `fill_segment` starts at index 2 on pass 0 / slice 0, so this is
117            // >= 1; `wrapping_sub` only keeps the function panic-free.
118            position.index.wrapping_sub(1)
119        } else if same_lane {
120            // core.c:215-217
121            //   position->slice * instance->segment_length + position->index - 1
122            position
123                .slice
124                .wrapping_mul(instance.segment_length)
125                .wrapping_add(position.index)
126                // core.c:217 `+ position->index - 1`, uint32_t arithmetic.
127                .wrapping_sub(1)
128        } else {
129            // core.c:219-221
130            //   position->slice * instance->segment_length
131            //       + ((position->index == 0) ? (-1) : 0)
132            //
133            // Adding the `int` -1 to a uint32_t is a DELIBERATE wrapping
134            // subtraction. `wrapping_sub(1)` is the same value as
135            // `wrapping_add(0xFFFF_FFFF)`; a plain `-` would panic in debug.
136            position
137                .slice
138                .wrapping_mul(instance.segment_length)
139                .wrapping_sub(u32::from(position.index == 0))
140        }
141    } else if same_lane {
142        // core.c:227-229
143        //   instance->lane_length - instance->segment_length
144        //       + position->index - 1
145        instance
146            .lane_length
147            // core.c:227 `lane_length - segment_length`, uint32_t arithmetic.
148            .wrapping_sub(instance.segment_length)
149            .wrapping_add(position.index)
150            // core.c:229 `+ position->index - 1`, uint32_t arithmetic.
151            .wrapping_sub(1)
152    } else {
153        // core.c:231-233
154        //   instance->lane_length - instance->segment_length
155        //       + ((position->index == 0) ? (-1) : 0)
156        instance
157            .lane_length
158            // core.c:231-232 `lane_length - segment_length`, uint32_t arithmetic.
159            .wrapping_sub(instance.segment_length)
160            // core.c:233, the same deliberate wrapping subtraction as above.
161            .wrapping_sub(u32::from(position.index == 0))
162    };
163
164    // core.c:239-242. 1.2.4. Mapping pseudo_rand to 0..<reference_area_size-1>
165    // and producing the relative position.
166    //
167    // Neither multiplication can overflow: `relative_position` is bounded by
168    // `u32::MAX` on entry and by `2^32 - 1` after the shift, and both factors of
169    // each product are therefore < 2^32.
170    let mut relative_position = u64::from(pseudo_rand);
171    relative_position = (relative_position * relative_position) >> 32;
172    // core.c:241 `reference_area_size - 1` is uint32_t (see the doc comment),
173    // hence the 32-bit `wrapping_sub` inside `u64::from(..)`.
174    relative_position = u64::from(reference_area_size.wrapping_sub(1))
175        // core.c:241-242, the outer subtraction is uint64_t. It cannot underflow
176        // — `(ras * rel) >> 32 <= ras - 1` for every `rel < 2^32` — but
177        // `wrapping_sub` keeps that a property rather than an assumption.
178        .wrapping_sub((u64::from(reference_area_size) * relative_position) >> 32);
179
180    // core.c:245-251. 1.2.5 Computing the starting position.
181    let mut start_position: u32 = 0;
182    if position.pass != 0 {
183        start_position = if position.slice == SYNC_POINTS - 1 {
184            0
185        } else {
186            position
187                .slice
188                .wrapping_add(1)
189                .wrapping_mul(instance.segment_length)
190        };
191    }
192
193    // core.c:254-255. 1.2.6. Computing the absolute position. `start_position`
194    // is uint32_t and `relative_position` uint64_t, so the sum and the `%` are
195    // evaluated in uint64_t and only the result is truncated back to uint32_t.
196    ((u64::from(start_position).wrapping_add(relative_position)) % u64::from(instance.lane_length))
197        as u32
198}
199
200// ---------------------------------------------------------------------------
201// Pre-hashing and the fill loop
202// ---------------------------------------------------------------------------
203
204/// `initial_hash()` from `src/core.c`, returning the 72-byte `H0` buffer.
205///
206/// BLAKE2b-512 over, in this order, each `u32` little-endian:
207/// `lanes`, `outlen`, `m_cost`, `t_cost`, `version`, `type`,
208/// then `pwdlen` and `pwd`, `saltlen` and `salt`, `secretlen` and `secret`,
209/// `adlen` and `ad`.
210///
211/// The first 64 bytes of the result are `H0`; the trailing 8 bytes are left
212/// zero and are filled in by [`fill_first_blocks`] with the block index and the
213/// lane index. (`initialize()` in the C zeroes them explicitly right after
214/// calling `initial_hash`; here they are never written in the first place.)
215///
216/// The four buffer lengths are hashed as `u32`, exactly as the C hashes
217/// `context->pwdlen` and friends. A caller that has run
218/// [`Params::validate_for`] first — which every entry point in this module
219/// does — cannot reach the truncating cast.
220///
221/// # Errors
222///
223/// Only a BLAKE2b parameter error, which cannot happen here: the digest length
224/// is the constant 64.
225pub fn initial_hash(
226    algorithm: Algorithm,
227    version: Version,
228    params: &Params,
229    pwd: &[u8],
230    salt: &[u8],
231    secret: &[u8],
232    ad: &[u8],
233) -> Result<[u8; PREHASH_SEED_LENGTH], Error> {
234    let mut blockhash = [0u8; PREHASH_SEED_LENGTH];
235    initial_hash_into(
236        algorithm,
237        version,
238        params,
239        pwd,
240        salt,
241        secret,
242        ad,
243        &mut blockhash,
244    )?;
245    Ok(blockhash)
246}
247
248/// [`initial_hash`] written directly into its caller's wipe-owned buffer.
249///
250/// The stable hash path uses this form so returning a 72-byte `Result` cannot
251/// make the optimiser leave an intermediate H0 copy behind on the stack.
252#[allow(clippy::too_many_arguments)]
253fn initial_hash_into(
254    algorithm: Algorithm,
255    version: Version,
256    params: &Params,
257    pwd: &[u8],
258    salt: &[u8],
259    secret: &[u8],
260    ad: &[u8],
261    blockhash: &mut [u8; PREHASH_SEED_LENGTH],
262) -> Result<(), Error> {
263    /// `store32(&value, len); blake2b_update(&BlakeHash, &value, 4);`
264    #[inline]
265    fn le32(len: usize) -> [u8; 4] {
266        (len as u32).to_le_bytes()
267    }
268
269    // core.c:547 `blake2b_init(&BlakeHash, ARGON2_PREHASH_DIGEST_LENGTH);`
270    let mut state = Blake2b::new(PREHASH_DIGEST_LENGTH)?;
271
272    // core.c:549-565. Six u32 parameters, in this exact order.
273    state.update(&params.lanes().to_le_bytes());
274    state.update(&le32(params.tag_len_bytes()));
275    state.update(&params.memory_kib().to_le_bytes());
276    state.update(&params.passes().to_le_bytes());
277    state.update(&version.as_u32().to_le_bytes());
278    state.update(&algorithm.as_u32().to_le_bytes());
279
280    // core.c:567-607. Four length-prefixed buffers, in this exact order.
281    // The C guards each `blake2b_update` with a NULL check; feeding an empty
282    // slice is the same no-op. `ARGON2_FLAG_CLEAR_PASSWORD` /
283    // `ARGON2_FLAG_CLEAR_SECRET` have no analogue here: this port never takes
284    // ownership of the caller's buffers, so it cannot wipe them.
285    state.update(&le32(pwd.len()));
286    state.update(pwd);
287
288    state.update(&le32(salt.len()));
289    state.update(salt);
290
291    state.update(&le32(secret.len()));
292    state.update(secret);
293
294    state.update(&le32(ad.len()));
295    state.update(ad);
296
297    // core.c:609 `blake2b_final(&BlakeHash, blockhash, ARGON2_PREHASH_DIGEST_LENGTH);`
298    state.finalize(&mut blockhash[..PREHASH_DIGEST_LENGTH])?;
299    Ok(())
300}
301
302/// `fill_first_blocks()` from `src/core.c`.
303///
304/// For each lane `l`, writes `LE32(0)` then `LE32(l)` at
305/// `blockhash[64..72]`, expands the 72 bytes to 1024 with `blake2b_long`, and
306/// loads that into block `l * lane_length + 0`; then repeats with `LE32(1)` for
307/// block `l * lane_length + 1`.
308///
309/// # Errors
310///
311/// [`Error::IncorrectParameter`] if `arena` is too small for
312/// `lanes * lane_length` blocks — unreachable from this module, which always
313/// sizes the arena from the same [`Params`]. Otherwise only a BLAKE2b parameter
314/// error, which cannot happen for the constant length 1024.
315pub fn fill_first_blocks(
316    blockhash: &mut [u8; PREHASH_SEED_LENGTH],
317    arena: &mut [Block],
318    lanes: u32,
319    lane_length: u32,
320) -> Result<(), Error> {
321    let mut blockhash_bytes = [0u8; BLOCK_SIZE];
322
323    // Unlike the C helper, this safe internal-api entry point reports a short
324    // arena instead of indexing unchecked. Keep every fallible exit inside the
325    // closure so the derived block bytes are wiped before the error escapes.
326    let result = (|| {
327        for lane in 0..lanes {
328            // core.c:522-523
329            //   store32(blockhash + ARGON2_PREHASH_DIGEST_LENGTH, 0);
330            //   store32(blockhash + ARGON2_PREHASH_DIGEST_LENGTH + 4, l);
331            blockhash[PREHASH_DIGEST_LENGTH..PREHASH_DIGEST_LENGTH + 4]
332                .copy_from_slice(&0u32.to_le_bytes());
333            blockhash[PREHASH_DIGEST_LENGTH + 4..PREHASH_SEED_LENGTH]
334                .copy_from_slice(&lane.to_le_bytes());
335
336            // core.c:524-525 `blake2b_long(blockhash_bytes, ARGON2_BLOCK_SIZE,
337            //                              blockhash, ARGON2_PREHASH_SEED_LENGTH);`
338            blake2b_long(&mut blockhash_bytes, blockhash)?;
339
340            // core.c:526-527 `load_block(&instance->memory[l * lane_length + 0], ..)`
341            let base = (lane as usize)
342                .checked_mul(lane_length as usize)
343                .ok_or(Error::IncorrectParameter)?;
344            match arena.get_mut(base) {
345                Some(block) => block.load_le(&blockhash_bytes),
346                None => return Err(Error::IncorrectParameter),
347            }
348
349            // core.c:529 `store32(blockhash + ARGON2_PREHASH_DIGEST_LENGTH, 1);`
350            blockhash[PREHASH_DIGEST_LENGTH..PREHASH_DIGEST_LENGTH + 4]
351                .copy_from_slice(&1u32.to_le_bytes());
352            blake2b_long(&mut blockhash_bytes, blockhash)?;
353
354            // core.c:532-533 `load_block(&instance->memory[l * lane_length + 1], ..)`
355            let second = base.checked_add(1).ok_or(Error::IncorrectParameter)?;
356            match arena.get_mut(second) {
357                Some(block) => block.load_le(&blockhash_bytes),
358                None => return Err(Error::IncorrectParameter),
359            }
360        }
361        Ok(())
362    })();
363
364    // core.c:535 `clear_internal_memory(blockhash_bytes, ARGON2_BLOCK_SIZE);`
365    clear_internal_memory(&mut blockhash_bytes);
366    result
367}
368
369/// `fill_memory_blocks()` from `src/core.c`, with the backend resolved from
370/// runtime CPU detection.
371///
372/// Safe, and the reason it can be: it never lets a caller name the backend. The
373/// value comes from [`crate::fill_block::backend`], which only ever returns a
374/// backend whose instruction set this CPU was *detected* to have. Every entry
375/// point that does take a [`Backend`] is `unsafe` — see
376/// [`fill_memory_blocks_traced`].
377///
378/// # Errors
379///
380/// [`Error::IncorrectParameter`] if `instance.lanes == 0` (`core.c:377`).
381pub fn fill_memory_blocks(instance: &Instance) -> Result<(), Error> {
382    // SAFETY: `backend()` is the cached result of the `is_*_feature_detected!`
383    // cascade in `fill_block::detect`, so this CPU can execute it. The other two
384    // obligations — a valid arena and no concurrent writer — are `Instance`'s
385    // own contract, discharged by whoever called `Instance::new`.
386    unsafe { fill_memory_blocks_traced(instance, crate::fill_block::backend(), None) }
387}
388
389/// `fill_memory_blocks()` with an explicit [`Backend`] and a KAT trace hook.
390///
391/// The function pointer is resolved **once**, here, before any loop:
392///
393/// ```text
394/// let fill = crate::fill_block::fill_segment_fn(backend);
395/// for pass in 0..instance.passes {
396///     for slice in 0..SYNC_POINTS {
397///         for lane in 0..instance.lanes {
398///             fill(instance, Position::new(pass, lane, slice, 0));
399///         }
400///     }
401/// }
402/// ```
403///
404/// Nothing detects or dispatches inside the per-block loop; the cost is one
405/// indirect call per *segment*, which is `segment_length` blocks.
406///
407/// `trace` is `internal_kat()` from `src/genkat.c`: it is invoked after every
408/// pass with `(pass_index, whole_arena)`, at a point where every helper is
409/// parked at the barrier and cannot touch the arena.
410///
411/// With the `parallel` feature, `instance.threads > 1` and
412/// `instance.lanes > 1`, one
413/// [`std::thread::scope`] owns the helpers for the whole fill. They meet at an
414/// atomic barrier after each slice, matching the C's sync points without
415/// respawning. The single-threaded path holds no raw-pointer sharing at all, so
416/// it stays checkable under Miri.
417///
418/// # Safety
419///
420/// `backend` selects a `fill_segment` carrying
421/// `#[target_feature(enable = ...)]`. Calling one whose feature this CPU lacks
422/// is undefined behaviour — in practice `SIGILL` — so the caller must guarantee
423/// the CPU can execute it. Either of these discharges that:
424///
425/// * `backend.is_available()` returned `true`, or
426/// * `backend` came from [`crate::fill_block::backend`] / `detect`.
427///
428/// The one other way to satisfy it is a host *measured* to execute the
429/// instructions while hiding them from `cpuid`, which is what the deliberately
430/// forced AVX2 tests rely on under Rosetta 2. That is a test-only affordance and
431/// never a library path.
432///
433/// `instance` must also uphold [`Instance::new`]'s contract, and no other thread
434/// may be filling this arena.
435///
436/// # Errors
437///
438/// [`Error::IncorrectParameter`] if `instance.lanes == 0`.
439pub unsafe fn fill_memory_blocks_traced(
440    instance: &Instance,
441    backend: Backend,
442    mut trace: Option<PassTrace<'_>>,
443) -> Result<(), Error> {
444    // core.c:377-379 `if (instance == NULL || instance->lanes == 0)`.
445    if instance.lanes == 0 {
446        return Err(Error::IncorrectParameter);
447    }
448
449    // Resolve the backend ONCE, before every loop. See `fill_block/mod.rs` for
450    // why the `#[target_feature]` boundary sits on `fill_segment`.
451    let fill = crate::fill_block::fill_segment_fn(backend);
452
453    // Multi-lane: hand the whole pass/slice/lane nest to the worker pool, which
454    // owns its threads for the entire fill instead of for one slice.
455    #[cfg(feature = "parallel")]
456    if instance.threads > 1 && instance.lanes > 1 {
457        // SAFETY: forwarded verbatim from this function's own contract.
458        unsafe { fill_pooled(instance, fill, trace) };
459        return Ok(());
460    }
461
462    for pass in 0..instance.passes {
463        for slice in 0..SYNC_POINTS {
464            // SAFETY: `fill` is `fill_segment_fn(backend)`, and the caller
465            // guarantees this CPU can execute `backend`; `instance` and the
466            // absence of a concurrent filler are the caller's obligations too.
467            unsafe { fill_slice_st(instance, fill, pass, slice) };
468        }
469
470        // genkat.c `internal_kat(instance, r)` — printed after each pass.
471        if let Some(callback) = trace.as_mut() {
472            // SAFETY: three obligations, and none of them is "the arena is zero".
473            //
474            //  1. Valid for `memory_len()` `Block`s: that is `Instance::new`'s
475            //     own contract, discharged by whoever built `instance`.
476            //  2. Every block is *initialised*, which is what makes a `&[Block]`
477            //     over them a valid reference. `Arena` guarantees this for its
478            //     whole capacity from birth (`alloc_zeroed`) and never gives it
479            //     up — initialised memory stays initialised when a `Workspace`
480            //     parks and re-lends it. NOTE for the next reader: this used to
481            //     be justified by "the arena was zero-initialised by
482            //     `Arena::new`". That premise is false for a pooled arena with
483            //     `zeroize-memory` off, and it was never the load-bearing one;
484            //     *initialised* is.
485            //  3. No live `&mut Block`: the parallel path returned above, and
486            //     every sequential `fill_slice_st` call has returned before
487            //     this shared slice is formed.
488            //
489            // Separately from soundness, the callback can never observe a
490            // previous tenant's bytes even on a reused arena: it fires only
491            // after all four slices of a pass have completed, and pass 0 writes
492            // every block of every lane exactly once before this point.
493            let blocks = unsafe {
494                core::slice::from_raw_parts(
495                    instance.memory_ptr().cast_const(),
496                    instance.memory_len(),
497                )
498            };
499            callback(pass, blocks);
500        }
501    }
502
503    Ok(())
504}
505
506/// `fill_memory_blocks_st()`'s innermost loop (`core.c:265-268`).
507///
508/// Deliberately free of any cross-thread pointer sharing: the only `unsafe` is
509/// the call to `fill`, which every backend requires. That keeps the whole
510/// single-threaded path checkable under Miri.
511///
512/// # Safety
513///
514/// As [`fill_memory_blocks_traced`]: this CPU must be able to execute whatever
515/// instruction set `fill` needs.
516unsafe fn fill_slice_st(instance: &Instance, fill: FillSegmentFn, pass: u32, slice: u32) {
517    for lane in 0..instance.lanes {
518        let position = Position::new(pass, lane, slice, 0);
519        // SAFETY: three obligations from `FillSegmentFn`.
520        //  1. `fill` came from `fill_segment_fn(backend)`, and this function's
521        //     caller guarantees the CPU can execute that backend — an
522        //     obligation that is now carried in the type system all the way out
523        //     to `fill_memory_blocks_traced`, `hash_traced` and
524        //     `hash_with_backend`, every one of which is an `unsafe fn`.
525        //  2. `instance`'s arena is valid for `memory_len()` blocks — that is
526        //     `Instance::new`'s own safety contract, discharged by the caller.
527        //  3. `pass < passes`, `slice < SYNC_POINTS` and `lane < lanes` by
528        //     construction, and this thread is the only one running, so nothing
529        //     else can be writing this segment.
530        unsafe { fill(instance, position) };
531    }
532}
533
534// ---------------------------------------------------------------------------
535// Parallel fill
536// ---------------------------------------------------------------------------
537
538/// A `&Instance` that may be handed to another thread.
539///
540/// `Instance` holds the arena as a `*mut Block`, which makes it neither `Send`
541/// nor `Sync`, so sharing it across lanes needs this newtype and the safety
542/// argument below.
543#[cfg(feature = "parallel")]
544#[derive(Clone, Copy)]
545struct SharedInstance<'a>(&'a Instance);
546
547// SAFETY: sending a `SharedInstance` hands another thread a `&Instance`, and
548// through it the arena's `*mut Block`. That is sound for the one way
549// `fill_slice_mt` uses it, and only that way:
550//
551//  * At each sync point, every worker in the whole-fill `std::thread::scope`
552//    runs the SAME `(pass, slice)` and pairwise DISTINCT `lane`s: lanes are
553//    claimed from a single `fetch_add` counter, so every index is handed out
554//    exactly once.
555//  * Within one slice, `fill_segment(instance, {pass, lane, slice, ..})`
556//    WRITES only blocks `lane * lane_length + slice * segment_length + i` for
557//    `i in 0..segment_length` — precisely the one segment that lane owns in
558//    this slice. Two different lanes therefore never write the same block, so
559//    no two `&mut Block` ever alias.
560//  * It READS `prev_offset`, which always stays inside its own lane (the block
561//    it just wrote, or the last block of the lane on the wrap-around), and
562//    `ref_lane * lane_length + index_alpha(..)`. When `ref_lane != lane`,
563//    `index_alpha`'s reference area is `slice * segment_length` blocks from the
564//    start of the lane (pass 0) or the `lane_length - segment_length` blocks of
565//    the *other three* slices (pass > 0, `start_position` skips the current
566//    one). Either way it never lands in another lane's current segment, which
567//    is the only region being written concurrently. So no read races a write.
568//  * The scope spans the whole fill. At each slice boundary, every helper
569//    publishes its writes with `Release` on `arrived`; the leader acquires them,
570//    resets the work counters, then releases the next `generation`, which every
571//    helper acquires before proceeding. Thus every write of slice `s`
572//    happens-before every read in slice `s + 1`, mirroring the C's sync points.
573//  * The `Instance` struct itself is only ever read; the interior mutability is
574//    confined to the arena it points at, via `Instance::block_mut`.
575//
576// `Sync` is deliberately NOT implemented: the workers each take their own copy
577// of this `Copy` newtype, so a shared reference to it never crosses a thread.
578#[cfg(feature = "parallel")]
579unsafe impl Send for SharedInstance<'_> {}
580
581/// The sync point, and the state the workers share across one whole fill.
582///
583/// # Why this exists instead of one `thread::scope` per slice
584///
585/// The four sync points per pass are algorithmic and cannot be weakened: every
586/// lane must finish slice `N` before any lane starts `N + 1`. What is *not*
587/// algorithmic is destroying and recreating the worker set at each of them,
588/// which is what `std::thread::scope` per slice did — `passes * 4 *
589/// (workers - 1)` thread creations for one hash, each a `clone()` plus a stack
590/// mapping. Measured on the target, four lanes over 4096-block segments:
591///
592/// ```text
593///   scope + 3 spawns, per slice        63.8 us
594///   live pool on std::sync::Barrier    20.7 us   (Mutex + Condvar: a syscall)
595///   live pool on this barrier           0.6 us
596/// ```
597///
598/// The threads now live for the whole fill and meet at a barrier instead. That
599/// is a 12-to-3 reduction in thread creations at `t = 1, p = 4`, and 48-to-3 at
600/// `t = 4`. The barrier is hand-rolled rather than `std::sync::Barrier` for the
601/// 20 us in that table: `Barrier` is a `Mutex` + `Condvar`, so every one of the
602/// `4 * passes` sync points is a pair of futex round trips.
603///
604/// # The barrier, and why the ordering is enough
605///
606/// Sense-reversing, with the *leader* — the thread that called into the hash —
607/// always doing the release. Every helper, having finished its lanes:
608///
609/// ```text
610///   arrived.fetch_add(1, Release)          publishes that helper's writes
611///   spin until generation != mine (Acquire)
612/// ```
613///
614/// and the leader, having finished its own lanes:
615///
616/// ```text
617///   spin until arrived + lost >= helpers (Acquire)
618///                                            acquires every live helper's writes
619///   ... KAT trace here, if any: everyone is parked ...
620///   next_lane = 0; arrived = 0
621///   generation.store(next, Release)        publishes all of it to everyone
622/// ```
623///
624/// The transitivity is the point. A helper's block writes happen-before its
625/// `Release` on `arrived`; the leader's `Acquire` on `arrived` makes them
626/// happen-before everything it does next, which includes its `Release` on
627/// `generation`; and every *other* helper's `Acquire` on `generation` therefore
628/// sees them. So lane 0's slice-`N` writes are visible to lane 3 in slice
629/// `N + 1`, which is exactly the guarantee `thread::scope`'s join gave for free.
630///
631/// # Panics, and why `lost` exists
632///
633/// A spin barrier turns a worker that never arrives into a hang, and a hang is
634/// a far worse failure than a panic. `Bail`'s `Drop` marks a helper lost on the
635/// way out of an unwind; the leader stops waiting, sets `stop`, and returns, at
636/// which point `thread::scope` joins and re-raises the original panic. Nothing
637/// in `fill_segment` is supposed to panic — but "supposed to" is not a
638/// scheduling primitive.
639#[cfg(feature = "parallel")]
640struct FillSync {
641    /// Lanes handed out for the current slice. Reset by the leader.
642    next_lane: core::sync::atomic::AtomicU32,
643    /// Helpers that have finished the current slice.
644    arrived: core::sync::atomic::AtomicU32,
645    /// Bumped once per sync point; helpers wait for their own count to match.
646    generation: core::sync::atomic::AtomicU32,
647    /// Helpers that unwound out of the fill and will never arrive again.
648    lost: core::sync::atomic::AtomicU32,
649    /// Tells parked helpers to give up so the scope can join and propagate.
650    stop: core::sync::atomic::AtomicBool,
651    /// Helpers the OS actually gave us, which is what the leader waits for.
652    helpers: u32,
653}
654
655/// Iterations of `pause` before falling back to `yield_now`.
656///
657/// A segment is thousands of blocks, so an arriving worker is normally a few
658/// microseconds ahead of the last one and spinning wins outright. Past that the
659/// box is oversubscribed — 4 vCPU here is 2 physical cores plus SMT — and
660/// yielding the slot to the worker we are waiting for is strictly better than
661/// stealing issue bandwidth from its SMT sibling.
662#[cfg(feature = "parallel")]
663const SPIN_LIMIT: u32 = 1024;
664
665#[cfg(feature = "parallel")]
666impl FillSync {
667    /// Wait until `cond()` holds, spinning then yielding.
668    #[inline]
669    fn park_until(mut cond: impl FnMut() -> bool) {
670        let mut spins = 0u32;
671        while !cond() {
672            if spins < SPIN_LIMIT {
673                spins += 1;
674                core::hint::spin_loop();
675            } else {
676                std::thread::yield_now();
677            }
678        }
679    }
680}
681
682/// Fill every lane of one `(pass, slice)` this worker can claim.
683///
684/// # Safety
685///
686/// As [`fill_memory_blocks_traced`]: this CPU must be able to execute whatever
687/// instruction set `fill` needs.
688#[cfg(feature = "parallel")]
689unsafe fn drain_lanes(
690    shared: SharedInstance<'_>,
691    sync: &FillSync,
692    fill: FillSegmentFn,
693    pass: u32,
694    slice: u32,
695    lanes: u32,
696) {
697    use core::sync::atomic::Ordering;
698
699    loop {
700        // Relaxed is enough: the counter only partitions work, and the
701        // happens-before the *data* needs comes from the barrier below.
702        let lane = sync.next_lane.fetch_add(1, Ordering::Relaxed);
703        if lane >= lanes {
704            return;
705        }
706        // SAFETY: as in `fill_slice_st`, plus the cross-lane argument written
707        // out at `unsafe impl Send for SharedInstance`: this worker owns lane
708        // `lane` of slice `slice` for the whole call, and no other worker was
709        // handed the same index.
710        unsafe { fill(shared.0, Position::new(pass, lane, slice, 0)) };
711    }
712}
713
714/// The whole pass/slice/lane nest, on a worker pool that outlives every slice.
715///
716/// Replaces `fill_memory_blocks_mt()` (`core.c:311-357`), which spawns one
717/// thread per lane *per slice* and caps concurrency by joining
718/// `thread[l - threads]` before creating thread `l`. This spawns
719/// `min(threads, lanes)` workers **once for the entire hash**; they pull lanes
720/// off a counter and meet at [`FillSync`]'s barrier at each of the `4 * passes`
721/// sync points. Same bound on live threads, same (absent) ordering requirement
722/// between lanes of one slice, same sync points — and `threads` never affects
723/// the tag, only `lanes` does, so the schedules are interchangeable.
724///
725/// # Safety
726///
727/// As [`fill_memory_blocks_traced`]: this CPU must be able to execute whatever
728/// instruction set `fill` needs.
729#[cfg(feature = "parallel")]
730unsafe fn fill_pooled(instance: &Instance, fill: FillSegmentFn, mut trace: Option<PassTrace<'_>>) {
731    use core::sync::atomic::{AtomicBool, AtomicU32, Ordering};
732
733    let lanes = instance.lanes;
734    let passes = instance.passes;
735    // `Instance::new` already clamps to `min(threads, lanes)`; re-clamp so a
736    // hand-built `Instance` cannot ask for more workers than there are lanes.
737    let workers = instance.threads.min(lanes);
738
739    let shared = SharedInstance(instance);
740    let sync = FillSync {
741        next_lane: AtomicU32::new(0),
742        arrived: AtomicU32::new(0),
743        generation: AtomicU32::new(0),
744        lost: AtomicU32::new(0),
745        stop: AtomicBool::new(false),
746        // Optimistic: the shortfall from any thread the OS refuses is charged
747        // to `lost` right after the spawn loop, which the leader's wait
748        // condition already accounts for.
749        helpers: workers.saturating_sub(1),
750    };
751    let sync = &sync;
752
753    std::thread::scope(|scope| {
754        /// Releases every parked helper when the leader leaves the scope,
755        /// however it leaves.
756        ///
757        /// Without this the crate deadlocks on any leader unwind — a panicking
758        /// KAT trace callback is enough to reach it. The helpers would be
759        /// spinning inside `park_until` for a `generation` bump that is never
760        /// coming, and `Scope`'s own `Drop` would block for ever trying to join
761        /// them. `thread::scope` propagating a panic is only useful if the
762        /// threads it is joining can still finish.
763        ///
764        /// Firing on the normal path too is harmless and is why this is a guard
765        /// rather than a `catch_unwind`: by then every helper has completed its
766        /// last slice and is on its way out, so `stop` only shortens a wait
767        /// whose answer has already been decided.
768        struct ReleaseHelpers<'a>(&'a FillSync);
769        impl Drop for ReleaseHelpers<'_> {
770            fn drop(&mut self) {
771                self.0.stop.store(true, Ordering::Relaxed);
772                self.0.generation.fetch_add(1, Ordering::Release);
773            }
774        }
775        let _release = ReleaseHelpers(sync);
776
777        let mut spawned = 0u32;
778
779        for _ in 1..workers {
780            // `Builder::spawn_scoped` returns an error where `scope.spawn`
781            // would panic, and this crate must not panic. A refused thread just
782            // means fewer workers: every lane is still claimed from `next_lane`
783            // by whoever does exist, and the tag does not depend on the count.
784            let handle = std::thread::Builder::new().spawn_scoped(scope, move || {
785                // Marks this helper lost if it unwinds, so the leader stops
786                // waiting for a barrier arrival that will never come.
787                struct Bail<'a>(&'a AtomicU32, bool);
788                impl Drop for Bail<'_> {
789                    fn drop(&mut self) {
790                        if self.1 {
791                            self.0.fetch_add(1, Ordering::Release);
792                        }
793                    }
794                }
795                let mut bail = Bail(&sync.lost, true);
796
797                let mut generation = 0u32;
798                'outer: for pass in 0..passes {
799                    for slice in 0..SYNC_POINTS {
800                        // SAFETY: forwarded from this function's contract — the
801                        // caller guarantees the CPU can execute `fill`. The
802                        // cross-thread half is the `unsafe impl Send for
803                        // SharedInstance` argument above.
804                        unsafe { drain_lanes(shared, sync, fill, pass, slice, lanes) };
805
806                        // Release: publishes this helper's block writes to the
807                        // leader's acquire below.
808                        sync.arrived.fetch_add(1, Ordering::Release);
809                        generation += 1;
810                        FillSync::park_until(|| {
811                            sync.generation.load(Ordering::Acquire) == generation
812                                || sync.stop.load(Ordering::Relaxed)
813                        });
814                        if sync.stop.load(Ordering::Relaxed) {
815                            break 'outer;
816                        }
817                    }
818                }
819                bail.1 = false;
820            });
821            if handle.is_ok() {
822                spawned += 1;
823            }
824        }
825
826        // A thread the OS refused must not be waited for. `helpers` was set
827        // optimistically; charge the shortfall to `lost`, which the leader's
828        // wait condition already accounts for.
829        sync.lost
830            .fetch_add(sync.helpers - spawned, Ordering::Relaxed);
831
832        let mut generation = 0u32;
833        'outer: for pass in 0..passes {
834            for slice in 0..SYNC_POINTS {
835                // SAFETY: as in the spawned workers; the leader is just one
836                // more of them.
837                unsafe { drain_lanes(shared, sync, fill, pass, slice, lanes) };
838
839                // Acquire: makes every helper's slice writes visible here, and
840                // therefore — through the release on `generation` below — to
841                // every other helper in the next slice.
842                FillSync::park_until(|| {
843                    sync.arrived.load(Ordering::Acquire) + sync.lost.load(Ordering::Acquire)
844                        >= sync.helpers
845                });
846                if sync.lost.load(Ordering::Relaxed) > sync.helpers - spawned {
847                    // A helper unwound. Stop cleanly so `thread::scope` can
848                    // join it and re-raise the panic, rather than spinning for
849                    // ever on an arrival that is never coming.
850                    sync.stop.store(true, Ordering::Relaxed);
851                    sync.generation.fetch_add(1, Ordering::Release);
852                    break 'outer;
853                }
854
855                // genkat.c `internal_kat(instance, r)`, printed after each
856                // pass. This is the one place it can go: every helper is parked
857                // on `generation`, holding no reference into the arena, and
858                // none of them can move until the release below.
859                if slice == SYNC_POINTS - 1
860                    && let Some(callback) = trace.as_mut()
861                {
862                    // SAFETY: three obligations, and none of them is "the arena
863                    // is zero".
864                    //
865                    //  1. Valid for `memory_len()` `Block`s: that is
866                    //     `Instance::new`'s own contract, discharged by whoever
867                    //     built `instance`.
868                    //  2. Every block is *initialised*, which is what makes a
869                    //     `&[Block]` over them a valid reference. `Arena`
870                    //     guarantees that for its whole capacity from birth —
871                    //     `alloc_zeroed`, or a kernel-zeroed `MAP_ANONYMOUS`
872                    //     mapping — and never gives it up.
873                    //  3. No live `&mut Block`: every helper has arrived at the
874                    //     barrier for the last slice of this pass and is parked
875                    //     inside `park_until`, so every `&mut Block` any of
876                    //     them formed is dead. This shared slice is the only
877                    //     live reference into the arena.
878                    let blocks = unsafe {
879                        core::slice::from_raw_parts(
880                            instance.memory_ptr().cast_const(),
881                            instance.memory_len(),
882                        )
883                    };
884                    callback(pass, blocks);
885                }
886
887                sync.next_lane.store(0, Ordering::Relaxed);
888                sync.arrived.store(0, Ordering::Relaxed);
889                generation += 1;
890                // Release: hands every helper everything acquired above.
891                sync.generation.store(generation, Ordering::Release);
892            }
893        }
894    });
895    // Leaving the scope joins every worker, and re-raises a helper's panic.
896}
897
898// ---------------------------------------------------------------------------
899// finalize
900// ---------------------------------------------------------------------------
901
902/// `finalize()` from `src/core.c`.
903///
904/// XORs the last block of every lane together, stores it little-endian, and runs
905/// `blake2b_long` over the 1024 bytes into `out`.
906///
907/// Freeing the arena — the `free_memory()` at the end of the C's `finalize` — is
908/// [`Arena`]'s `Drop`, which wipes before it deallocates. On the pooled path
909/// (`Hasher`) the same wipe happens in [`Workspace::release`] instead, and
910/// only the `dealloc` is deferred; either way the arena is wiped by the time the
911/// call that owned it returns.
912///
913/// # Errors
914///
915/// [`Error::IncorrectParameter`] for a degenerate instance whose last blocks are
916/// out of bounds; otherwise only a BLAKE2b parameter error, which cannot happen
917/// for a validated `outlen`.
918pub fn finalize(instance: &Instance, out: &mut [u8]) -> Result<(), Error> {
919    let lane_length = instance.lane_length as usize;
920    if lane_length == 0 || instance.lanes == 0 {
921        return Err(Error::IncorrectParameter);
922    }
923
924    // SAFETY: `Instance`'s contract guarantees `memory_ptr()` is valid for
925    // `memory_len()` initialised `Block`s. `fill_memory_blocks` has returned, so
926    // every worker is joined and no `&mut Block` into the arena is live; this
927    // shared slice is the only live reference to it.
928    let blocks = unsafe {
929        core::slice::from_raw_parts(instance.memory_ptr().cast_const(), instance.memory_len())
930    };
931
932    // core.c:160 `copy_block(&blockhash, instance->memory + instance->lane_length - 1);`
933    let Some(&last_of_lane_0) = blocks.get(lane_length - 1) else {
934        return Err(Error::IncorrectParameter);
935    };
936    let mut blockhash = last_of_lane_0;
937
938    // core.c:163-167. XOR the last block of every other lane.
939    for lane in 1..instance.lanes {
940        // `l * instance->lane_length + (instance->lane_length - 1)`
941        let index = (lane as usize)
942            .checked_mul(lane_length)
943            .and_then(|base| base.checked_add(lane_length - 1))
944            .ok_or(Error::IncorrectParameter)?;
945        match blocks.get(index) {
946            Some(block) => blockhash.xor_with(block),
947            None => return Err(Error::IncorrectParameter),
948        }
949    }
950
951    // core.c:171-174 `store_block(blockhash_bytes, &blockhash);`
952    //                `blake2b_long(context->out, context->outlen,
953    //                              blockhash_bytes, ARGON2_BLOCK_SIZE);`
954    let mut blockhash_bytes = blockhash.to_le_bytes();
955    let result = blake2b_long(out, &blockhash_bytes);
956
957    // core.c:176-177, on every path.
958    clear_internal_memory_u64(&mut blockhash.0);
959    clear_internal_memory(&mut blockhash_bytes);
960
961    result
962}
963
964/// `argon2_compare()` from `src/argon2.c`: a constant-time byte comparison.
965///
966/// Returns `false` immediately for differing lengths (the C never compares
967/// mismatched lengths — `outlen` is fixed by the decoded string).
968#[must_use]
969pub fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
970    if a.len() != b.len() {
971        return false;
972    }
973
974    let mut d = 0u8;
975    for (x, y) in a.iter().zip(b.iter()) {
976        d |= x ^ y;
977    }
978
979    // The accumulator is laundered before it is tested. Nothing downstream may
980    // learn that `d` is only ever compared against zero, because a compiler
981    // that knows *that* is free to rewrite the loop above into a `bcmp` that
982    // stops at the first differing byte — which is precisely the timing leak
983    // this function exists to prevent.
984    //
985    // What this actually emits on aarch64 (`--emit=asm`, release):
986    //
987    //     LBB_2: ldrb w9,[x0],#1 ; ldrb w10,[x2],#1
988    //            eor  w9,w10,w9  ; orr  w8,w9,w8
989    //            subs x1,x1,#1   ; b.ne LBB_2      <- branch on the COUNTER
990    //            strb w8,[sp,#15]; ldrb w8,[sp,#15] <- the black_box launder
991    //            sub  w8,w8,#1   ; ubfx w0,w8,#8,#1 <- branchless verdict
992    //
993    // One branch, and it is the loop counter; no `bcmp`, no `memcmp`, nothing
994    // that depends on the bytes. The launder costs two instructions once per
995    // call.
996    //
997    // `black_box` rather than the `asm!` barrier in `memory::secure_wipe_raw`
998    // because the thing being protected is a value in a register, not a store
999    // to memory, and because it exists on every target — including wasm and
1000    // under Miri, where `asm!` does not.
1001    let d = core::hint::black_box(d);
1002
1003    // argon2.c:246 `return (int)((1 & ((d - 1) >> 8)) - 1);` — 0 when `d == 0`,
1004    // -1 otherwise, computed without a branch. Written out rather than reduced
1005    // to `d == 0` so the constant-time property is on the page, not implied.
1006    let verdict = (1i32 & ((i32::from(d) - 1) >> 8)) - 1;
1007    verdict == 0
1008}
1009
1010// ---------------------------------------------------------------------------
1011// Public API
1012// ---------------------------------------------------------------------------
1013
1014/// Salt length used by the `*_with_random_salt` entry points, in bytes.
1015///
1016/// 16 is what RFC 9106 §4 recommends for password hashing, and is comfortably
1017/// above [`crate::params::MIN_SALT_LENGTH`]. It is a constant rather than an
1018/// argument because a caller who wants to choose the length also wants to
1019/// choose the bytes, and should call [`Argon2::hash_encoded`] with their own
1020/// salt instead.
1021#[cfg(feature = "std")]
1022pub const RANDOM_SALT_LEN: usize = 16;
1023
1024/// Longest salt the `*_bounded` verify entry points will accept, in bytes.
1025///
1026/// [`Params`] carries no salt length, so the pre-decode size gate in
1027/// [`Argon2::verify_encoded_bounded`] needs one number from somewhere. This is
1028/// it: generous enough that no real producer is near it — RFC 9106 recommends
1029/// 16 and [`RANDOM_SALT_LEN`] uses that — and small enough that a hostile string
1030/// cannot turn the decode into an allocation worth caring about.
1031///
1032/// A legitimate string with a salt longer than this is rejected with
1033/// [`Error::DecodingLengthFail`]; use the unbounded
1034/// [`Argon2::verify_encoded`] if you genuinely have one.
1035pub const BOUNDED_MAX_SALT_LEN: u32 = 1024;
1036
1037/// A configured Argon2 hasher.
1038///
1039/// Bundles the algorithm, version and validated [`Params`]. The secret (key) and
1040/// associated data are passed per call rather than stored, which keeps `Argon2`
1041/// free of lifetime parameters.
1042///
1043/// # Examples
1044///
1045/// ```
1046/// use argon2_rust::{Algorithm, Argon2, Params, Version};
1047///
1048/// // m=19456 KiB, t=2, 1 lane, 32-byte tag: `Params::default()`, which is
1049/// // what a password store should start from. About 8 ms per hash in release,
1050/// // so this runs as a real doctest rather than only being compiled.
1051/// let params = Params::default();
1052/// let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
1053/// let mut tag = [0u8; 32];
1054/// argon2.hash_into(b"password", b"somesalt", &mut tag)?;
1055/// assert_eq!(argon2.verify(b"password", b"somesalt", &tag), Ok(()));
1056/// # Ok::<(), argon2_rust::Error>(())
1057/// ```
1058///
1059/// # Two spellings
1060///
1061/// Three entry points carry a password-flavoured alias, and only three:
1062/// [`Argon2::hash_password_into`] for [`Argon2::hash_into`],
1063/// [`Argon2::hash_password`] for [`Argon2::hash_encoded`], and
1064/// [`Argon2::verify_password`] for [`Argon2::verify_encoded`]. Each of those
1065/// three is a pure delegation, same function and same bytes.
1066///
1067/// Six other entry points have a base name and nothing else: [`Argon2::hash`],
1068/// [`Argon2::verify`], [`Argon2::hash_into_with_ad`],
1069/// [`Argon2::verify_encoded_with_ad`], [`Argon2::verify_encoded_bounded`] and
1070/// [`Argon2::verify_encoded_bounded_with_ad`]. One runs the other way:
1071/// `Argon2::hash_password_with_random_salt` has a password name with no base
1072/// twin, and it is not a delegation either. It draws a fresh salt from the OS
1073/// before calling [`Argon2::hash_encoded`], so two calls with one password do
1074/// not return the same string.
1075///
1076/// Where the alias does exist, the two families do not spell the *output
1077/// format* the same way:
1078///
1079/// ```text
1080///                      raw -> caller buffer   raw -> Vec   PHC -> String
1081///   base:              hash_into              hash         hash_encoded
1082///   password:          hash_password_into     (none)       hash_password
1083///                                   ^ raw                  ^ PHC
1084/// ```
1085///
1086/// [`Argon2::hash_password_into`] writes a **raw** tag into `out`, byte for
1087/// byte what [`Argon2::hash_into`] writes. [`Argon2::hash_password`] returns a
1088/// **PHC string**, character for character what [`Argon2::hash_encoded`]
1089/// returns. The only difference between those two names is `_into`, which reads
1090/// as a destination and not as a format; in the base family the word `encoded`
1091/// carries that distinction in the name, and in the password family nothing
1092/// does. Verification is the same shape: [`Argon2::verify_password`] takes a
1093/// PHC string, like [`Argon2::verify_encoded`], not the raw expected tag that
1094/// [`Argon2::verify`] takes.
1095///
1096/// There is no raw-`Vec` password spelling, which is the empty cell above; for
1097/// that shape the only name is [`Argon2::hash`].
1098///
1099/// ```
1100/// use argon2_rust::{Algorithm, Argon2, Params, Version, params::Memory};
1101///
1102/// let params = Params::builder().memory(Memory::kib(1 << 8)).passes(1).build()?;
1103/// let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
1104///
1105/// // `_into` picks the destination, and with it the raw format.
1106/// let mut raw = [0u8; 32];
1107/// argon2.hash_password_into(b"password", b"somesalt", &mut raw)?;
1108///
1109/// // No suffix at all, and the format changes to PHC.
1110/// let phc = argon2.hash_password(b"password", b"somesalt")?;
1111/// assert!(phc.starts_with("$argon2id$v=19$m=256,t=1,p=1$c29tZXNhbHQ$"));
1112///
1113/// // One tag underneath both: `raw` is the bytes the string base64s.
1114/// assert_eq!(argon2.hash(b"password", b"somesalt")?, raw);
1115/// # Ok::<(), argon2_rust::Error>(())
1116/// ```
1117#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1118pub struct Argon2 {
1119    algorithm: Algorithm,
1120    version: Version,
1121    params: Params,
1122}
1123
1124impl Argon2 {
1125    /// Build a hasher. `params` is already validated, so this cannot fail.
1126    #[inline]
1127    #[must_use]
1128    pub const fn new(algorithm: Algorithm, version: Version, params: Params) -> Argon2 {
1129        Argon2 {
1130            algorithm,
1131            version,
1132            params,
1133        }
1134    }
1135
1136    /// The configured algorithm.
1137    #[inline]
1138    #[must_use]
1139    pub const fn algorithm(&self) -> Algorithm {
1140        self.algorithm
1141    }
1142
1143    /// The configured version.
1144    #[inline]
1145    #[must_use]
1146    pub const fn version(&self) -> Version {
1147        self.version
1148    }
1149
1150    /// The configured parameters.
1151    #[inline]
1152    #[must_use]
1153    pub const fn params(&self) -> &Params {
1154        &self.params
1155    }
1156
1157    /// A `Hasher`: this configuration plus scratch memory it keeps between
1158    /// calls.
1159    ///
1160    /// Allocates nothing — the first hash allocates the arena, and every hash
1161    /// after that reuses it. Use this when one thread hashes repeatedly;
1162    /// keep using [`Argon2::hash_into`] and friends when it does not.
1163    ///
1164    /// ```
1165    /// use argon2_rust::{Algorithm, Argon2, Params, Version, params::Memory};
1166    ///
1167    /// let params = Params::builder().memory(Memory::kib(8)).passes(1).build()?;
1168    /// let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
1169    ///
1170    /// let mut hasher = argon2.hasher();
1171    /// let mut tag = [0u8; 32];
1172    /// for salt in [&b"somesalt"[..], &b"othersaltx"[..]] {
1173    ///     hasher.hash_into(b"password", salt, &mut tag)?;
1174    /// }
1175    ///
1176    /// // Same answer as the one-shot API, every time.
1177    /// let mut once = [0u8; 32];
1178    /// argon2.hash_into(b"password", b"somesalt", &mut once)?;
1179    /// hasher.hash_into(b"password", b"somesalt", &mut tag)?;
1180    /// assert_eq!(tag, once);
1181    /// # Ok::<(), argon2_rust::Error>(())
1182    /// ```
1183    #[inline]
1184    #[must_use]
1185    pub fn hasher(&self) -> Hasher {
1186        Hasher {
1187            argon2: *self,
1188            workspace: Workspace::new(),
1189        }
1190    }
1191
1192    /// Derive a tag into `out`.
1193    ///
1194    /// `out.len()` must equal [`Params::tag_len_bytes`].
1195    ///
1196    /// ```
1197    /// use argon2_rust::{Algorithm, Argon2, Error, Params, Version, params::Memory};
1198    ///
1199    /// let params = Params::builder().memory(Memory::kib(64)).passes(1).build()?;
1200    /// let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
1201    ///
1202    /// let mut tag = [0u8; 32];
1203    /// argon2.hash_into(b"password", b"somesalt", &mut tag)?;
1204    ///
1205    /// // Those 32 bytes are what the PHC string base64s, so pinning the string
1206    /// // pins the tag without spelling out an array of hex.
1207    /// assert_eq!(
1208    ///     argon2.hash_encoded(b"password", b"somesalt")?,
1209    ///     "$argon2id$v=19$m=64,t=1,p=1$c29tZXNhbHQ$cpx6VEQbwTVZvcpxNIxOVUWZ5xnAipUmAe1cg2GMG70",
1210    /// );
1211    ///
1212    /// // `out.len()` is checked against `Params::tag_len_bytes`, never used to
1213    /// // size the tag: a buffer of the wrong length is an error, not a
1214    /// // truncated hash.
1215    /// let mut too_short = [0u8; 16];
1216    /// assert_eq!(
1217    ///     argon2.hash_into(b"password", b"somesalt", &mut too_short),
1218    ///     Err(Error::OutPtrMismatch),
1219    /// );
1220    /// # Ok::<(), argon2_rust::Error>(())
1221    /// ```
1222    ///
1223    /// # Errors
1224    ///
1225    /// Whatever [`Params::validate_for`] returns, [`Error::OutPtrMismatch`] if
1226    /// `out.len()` disagrees with `params.tag_len_bytes()`, or
1227    /// [`Error::MemoryAllocationError`].
1228    pub fn hash_into(&self, pwd: &[u8], salt: &[u8], out: &mut [u8]) -> Result<(), Error> {
1229        self.hash_into_with_ad(pwd, salt, &[], &[], out)
1230    }
1231
1232    /// Derive a tag into `out`, with a secret key and associated data.
1233    ///
1234    /// No PHC-emitting entry point accepts either; see the "Secret and
1235    /// associated data" section of [`Argon2::hash_encoded`].
1236    ///
1237    /// # Errors
1238    ///
1239    /// As [`Argon2::hash_into`].
1240    pub fn hash_into_with_ad(
1241        &self,
1242        pwd: &[u8],
1243        salt: &[u8],
1244        secret: &[u8],
1245        ad: &[u8],
1246        out: &mut [u8],
1247    ) -> Result<(), Error> {
1248        // SAFETY: the only `Backend` the public API ever names is
1249        // `fill_block::backend()`, the cached result of the
1250        // `is_*_feature_detected!` cascade, so this CPU can execute it by
1251        // construction. That is what keeps this — and every other public entry
1252        // point — safe while `hash_inner` is not.
1253        unsafe {
1254            hash_inner(
1255                crate::fill_block::backend(),
1256                self.algorithm,
1257                self.version,
1258                &self.params,
1259                pwd,
1260                salt,
1261                secret,
1262                ad,
1263                out,
1264            )
1265        }
1266    }
1267
1268    /// Derive a tag of [`Params::tag_len_bytes`] bytes.
1269    ///
1270    /// ```
1271    /// use argon2_rust::{Algorithm, Argon2, Params, Version, params::Memory};
1272    ///
1273    /// let params = Params::builder().memory(Memory::kib(64)).passes(1).build()?;
1274    /// let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
1275    ///
1276    /// // The `Vec` is sized from the parameters, so there is no buffer to get
1277    /// // wrong and no `Error::OutPtrMismatch` to handle.
1278    /// let tag = argon2.hash(b"password", b"somesalt")?;
1279    /// assert_eq!(tag.len(), argon2.params().tag_len_bytes());
1280    ///
1281    /// // Byte for byte what `hash_into` writes into a buffer you own; this is
1282    /// // the same function with the allocation moved inside.
1283    /// let mut into = [0u8; 32];
1284    /// argon2.hash_into(b"password", b"somesalt", &mut into)?;
1285    /// assert_eq!(tag, into);
1286    /// # Ok::<(), argon2_rust::Error>(())
1287    /// ```
1288    ///
1289    /// # Errors
1290    ///
1291    /// As [`Argon2::hash_into`].
1292    pub fn hash(&self, pwd: &[u8], salt: &[u8]) -> Result<Vec<u8>, Error> {
1293        let mut out = try_zeroed_vec(self.params.tag_len_bytes())?;
1294        self.hash_into(pwd, salt, &mut out)?;
1295        Ok(out)
1296    }
1297
1298    /// Derive a tag and format it as a PHC string.
1299    ///
1300    /// Always emits `$v=`, exactly as `encode_string()` in the C does, even for
1301    /// [`Version::V0x10`].
1302    ///
1303    /// # Secret and associated data
1304    ///
1305    /// No PHC-emitting entry point takes a `secret` (pepper) or `ad`, because a
1306    /// PHC string has a field for neither: `argon2_hash()` (`argon2.h:322`)
1307    /// hardcodes `context.secret = NULL; context.ad = NULL` (`argon2.c:139-142`)
1308    /// and `encode_string` emits only `$type$v=$m=,t=,p=$salt$hash`. A peppered
1309    /// deployment must call [`Argon2::hash_into_with_ad`] and encode the tag
1310    /// itself — `mod encoding` is private, so that means the PHC layout and
1311    /// unpadded Base64 by hand. Its string is indistinguishable from an
1312    /// unpeppered one, so [`Argon2::verify_encoded`] on it answers
1313    /// [`Error::VerifyMismatch`] rather than any "missing pepper" signal.
1314    ///
1315    /// # Errors
1316    ///
1317    /// As [`Argon2::hash_into`], plus [`Error::EncodingFail`].
1318    pub fn hash_encoded(&self, pwd: &[u8], salt: &[u8]) -> Result<String, Error> {
1319        let mut tag = self.hash(pwd, salt)?;
1320        let encoded = crate::encoding::encode_string_alloc(
1321            self.algorithm,
1322            self.version,
1323            &self.params,
1324            salt,
1325            &tag,
1326        );
1327        // argon2.c:173 `clear_internal_memory(out, hashlen);`
1328        clear_internal_memory(&mut tag);
1329        encoded
1330    }
1331
1332    /// Recompute the tag and compare it with `expected` in constant time.
1333    ///
1334    /// A length mismatch is a [`Error::VerifyMismatch`], not a separate error:
1335    /// the C cannot reach that case, because `decode_string` sets
1336    /// `context->outlen` from the tag it just decoded.
1337    ///
1338    /// ```
1339    /// use argon2_rust::{Algorithm, Argon2, Error, Params, Version, params::Memory};
1340    ///
1341    /// let params = Params::builder().memory(Memory::kib(64)).passes(1).build()?;
1342    /// let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
1343    ///
1344    /// // A raw tag stored earlier, alongside the salt that produced it. The
1345    /// // parameters are yours to remember too, which is what the PHC string
1346    /// // from `hash_encoded` saves you.
1347    /// let expected = argon2.hash(b"password", b"somesalt")?;
1348    /// assert_eq!(argon2.verify(b"password", b"somesalt", &expected), Ok(()));
1349    ///
1350    /// // Wrong password.
1351    /// assert_eq!(
1352    ///     argon2.verify(b"wrong", b"somesalt", &expected),
1353    ///     Err(Error::VerifyMismatch),
1354    /// );
1355    /// // Wrong salt: the tag is a function of both.
1356    /// assert_eq!(
1357    ///     argon2.verify(b"password", b"othersalt", &expected),
1358    ///     Err(Error::VerifyMismatch),
1359    /// );
1360    /// // A truncated `expected` is that same error and not a length error,
1361    /// // exactly as the paragraph above says.
1362    /// assert_eq!(
1363    ///     argon2.verify(b"password", b"somesalt", &expected[..16]),
1364    ///     Err(Error::VerifyMismatch),
1365    /// );
1366    /// # Ok::<(), argon2_rust::Error>(())
1367    /// ```
1368    ///
1369    /// # Errors
1370    ///
1371    /// As [`Argon2::hash_into`], or [`Error::VerifyMismatch`].
1372    pub fn verify(&self, pwd: &[u8], salt: &[u8], expected: &[u8]) -> Result<(), Error> {
1373        let mut computed = try_zeroed_vec(self.params.tag_len_bytes())?;
1374        let result = self.hash_into(pwd, salt, &mut computed);
1375        // argon2.c:349 `argon2_compare(hash, context->out, context->outlen)`.
1376        let matched = result.is_ok() && constant_time_eq(&computed, expected);
1377        clear_internal_memory(&mut computed);
1378
1379        result?;
1380        if matched {
1381            Ok(())
1382        } else {
1383            Err(Error::VerifyMismatch)
1384        }
1385    }
1386
1387    /// `argon2_verify()`: decode a PHC string and check `pwd` against it.
1388    ///
1389    /// # Errors
1390    ///
1391    /// [`Error::DecodingFail`] for a malformed string, [`Error::VerifyMismatch`]
1392    /// if the password is wrong, or any hashing error.
1393    pub fn verify_encoded(encoded: &str, pwd: &[u8], algorithm: Algorithm) -> Result<(), Error> {
1394        // argon2.c:260-262 `if (pwdlen > ARGON2_MAX_PWD_LENGTH)`.
1395        if pwd.len() > MAX_PWD_LENGTH as usize {
1396            return Err(Error::PwdTooLong);
1397        }
1398
1399        // argon2.c:289 `decode_string(&ctx, encoded, type)`.
1400        let decoded = crate::encoding::decode_string(encoded, algorithm)?;
1401
1402        // argon2.c:302 `argon2_verify_ctx(&ctx, desired_result, type)`.
1403        Argon2::new(decoded.algorithm, decoded.version, decoded.params).verify(
1404            pwd,
1405            &decoded.salt,
1406            &decoded.hash,
1407        )
1408    }
1409
1410    /// `argon2_verify_ctx()`: decode a PHC string and check `pwd` against it,
1411    /// with a secret key and associated data.
1412    ///
1413    /// # Errors
1414    ///
1415    /// As [`Argon2::verify_encoded`], plus the secret/ad validation errors of
1416    /// [`Argon2::hash_into_with_ad`].
1417    pub fn verify_encoded_with_ad(
1418        encoded: &str,
1419        pwd: &[u8],
1420        secret: &[u8],
1421        ad: &[u8],
1422        algorithm: Algorithm,
1423    ) -> Result<(), Error> {
1424        // argon2.c:260-262 `if (pwdlen > ARGON2_MAX_PWD_LENGTH)`.
1425        if pwd.len() > MAX_PWD_LENGTH as usize {
1426            return Err(Error::PwdTooLong);
1427        }
1428
1429        // argon2.c:289 `decode_string(&ctx, encoded, type)`.
1430        let decoded = crate::encoding::decode_string(encoded, algorithm)?;
1431
1432        // argon2.c:302 `argon2_verify_ctx(&ctx, desired_result, type)`.
1433        let argon2 = Argon2::new(decoded.algorithm, decoded.version, decoded.params);
1434        let mut computed = try_zeroed_vec(argon2.params.tag_len_bytes())?;
1435        let result =
1436            argon2.hash_into_with_ad(pwd, &decoded.salt, secret, ad, &mut computed);
1437        let matched = result.is_ok() && constant_time_eq(&computed, &decoded.hash);
1438        clear_internal_memory(&mut computed);
1439
1440        result?;
1441        if matched {
1442            Ok(())
1443        } else {
1444            Err(Error::VerifyMismatch)
1445        }
1446    }
1447
1448    // -----------------------------------------------------------------
1449    // Password-flavoured spellings of the three entry points above
1450    // -----------------------------------------------------------------
1451    //
1452    // Same functions, the names the C's three public entry points suggest:
1453    // `argon2_hash` with a raw output buffer, `argon2_hash` with an encoded
1454    // output buffer, and `argon2_verify`. They exist so a caller can read the
1455    // API as "hash a password" rather than "hash some bytes"; the shorter
1456    // spellings stay because that is what this crate's own tests and benches
1457    // already call.
1458    //
1459    // That last half is an internal reason. The user-facing one is that these
1460    // are the names a C caller already knows: the per-algorithm wrappers it
1461    // links against are `argon2id_hash_raw` (argon2.c:230),
1462    // `argon2id_hash_encoded` (argon2.c:219) and `argon2id_verify`
1463    // (argon2.c:325), each a single `return` into `argon2_hash`/`argon2_verify`
1464    // and nothing else in the body. Only `argon2id_verify` fits on one line
1465    // (argon2.c:327); the two hash wrappers each spend three on the argument
1466    // list alone (argon2.c:225-227 and argon2.c:234-236), which is line
1467    // wrapping and not work. The raw/encoded choice is made entirely by which
1468    // out-pointer those wrappers pass as non-NULL (argon2.c:160 `if (hash)`,
1469    // argon2.c:165 `if (encoded && encodedlen)`).
1470    //
1471    // Note what the C's names do that these do not: they carry the output
1472    // format, `raw` against `encoded`. Here the only difference between
1473    // `hash_password_into` and `hash_password` is `_into`, which names a
1474    // destination, not a format. So the format asymmetry is stated on the type
1475    // (`Argon2`'s `# Two spellings`) and again on the first line of each method
1476    // below, where a reader scanning the method list will actually see it.
1477
1478    /// Derive a **raw** tag into `out`, not a PHC string.
1479    ///
1480    /// `argon2_hash()` with `hash != NULL` (`argon2.c:160`). The same function
1481    /// as [`Argon2::hash_into`]: `_into` picks the destination, and the format
1482    /// that comes with it is bytes. `out.len()` must equal
1483    /// [`Params::tag_len_bytes`]. For the PHC string, [`Argon2::hash_password`].
1484    ///
1485    /// # Errors
1486    ///
1487    /// As [`Argon2::hash_into`].
1488    #[inline]
1489    pub fn hash_password_into(&self, pwd: &[u8], salt: &[u8], out: &mut [u8]) -> Result<(), Error> {
1490        self.hash_into(pwd, salt, out)
1491    }
1492
1493    /// Derive a tag and return the **PHC string** for it, not the raw bytes.
1494    ///
1495    /// `argon2_hash()` with `encoded != NULL` (`argon2.c:165`). The same
1496    /// function as [`Argon2::hash_encoded`]; for the raw tag, its sibling
1497    /// [`Argon2::hash_password_into`] or [`Argon2::hash`].
1498    ///
1499    /// Always emits `$v=`, just like `encode_string()` in the C, even for
1500    /// [`Version::V0x10`] — the `v=0x10` reference strings in `src/test.c`
1501    /// predate that field, so they have no `$v=` and are one field shorter than
1502    /// what this returns. Both forms decode, see [`Argon2::verify_password`].
1503    ///
1504    /// # Errors
1505    ///
1506    /// As [`Argon2::hash_into`], plus [`Error::EncodingFail`].
1507    #[inline]
1508    pub fn hash_password(&self, pwd: &[u8], salt: &[u8]) -> Result<String, Error> {
1509        self.hash_encoded(pwd, salt)
1510    }
1511
1512    /// Derive a PHC string with a fresh salt from the OS entropy source.
1513    ///
1514    /// Convenience for the common case where the caller does not manage its own
1515    /// salt. The salt is [`RANDOM_SALT_LEN`] bytes — the length RFC 9106 §4
1516    /// recommends — and lands in the returned string, so verification needs
1517    /// nothing else kept alongside it.
1518    ///
1519    /// The randomness comes straight from the OS, with the entry point chosen
1520    /// per platform (`getrandom(2)`, `getentropy`, `CCRandomGenerateBytes`,
1521    /// `ProcessPrng`, WASI `random_get`, or `/dev/urandom`) and declared by
1522    /// hand, so this costs the crate no dependency. Callers who already run
1523    /// their own CSPRNG should keep passing their own salt to
1524    /// [`Argon2::hash_encoded`].
1525    ///
1526    /// Hashing many passwords? [`Hasher::hash_password_with_random_salt`] does
1527    /// this over a pooled arena.
1528    ///
1529    /// # Errors
1530    ///
1531    /// [`Error::OsRandom`] if every OS entropy source for this platform fails,
1532    /// plus the errors of [`Argon2::hash_encoded`].
1533    #[cfg(feature = "std")]
1534    pub fn hash_password_with_random_salt(&self, pwd: &[u8]) -> Result<String, Error> {
1535        // Not wiped on the way out, deliberately, and unlike every other
1536        // buffer in this file: the salt is *published* in the returned string,
1537        // so scrubbing the stack copy protects nothing that is not already in
1538        // the caller's hands. `clear_internal_memory` is for secret-derived
1539        // material; a salt is not that.
1540        let mut salt = [0u8; RANDOM_SALT_LEN];
1541        crate::random::os_random(&mut salt)?;
1542        self.hash_encoded(pwd, &salt)
1543    }
1544
1545    /// Check `pwd` against a **PHC string**, not against a raw tag.
1546    ///
1547    /// `argon2_verify()` (`argon2.c:249`): decode `encoded`, then recompute and
1548    /// compare. The same function as [`Argon2::verify_encoded`]. The parameters
1549    /// come out of the string, so nothing on `self` is consulted, which is why
1550    /// this is an associated function. To check a raw expected tag with these
1551    /// parameters instead, [`Argon2::verify`].
1552    ///
1553    /// # Errors
1554    ///
1555    /// [`Error::DecodingFail`] for a malformed string, [`Error::VerifyMismatch`]
1556    /// if the password is wrong, or any hashing error.
1557    #[inline]
1558    pub fn verify_password(encoded: &str, pwd: &[u8], algorithm: Algorithm) -> Result<(), Error> {
1559        Argon2::verify_encoded(encoded, pwd, algorithm)
1560    }
1561
1562    /// [`Argon2::verify_encoded`], refusing costs above `ceiling` **before**
1563    /// allocating anything.
1564    ///
1565    /// # Why this exists
1566    ///
1567    /// `m_cost` in a PHC string is up to ten decimal digits, and the decoder
1568    /// accepts everything the C accepts — up to
1569    /// [`MAX_MEMORY`](crate::params::MAX_MEMORY) KiB, which is 4 TiB. Nothing in
1570    /// [`Argon2::verify_encoded`] sits between that number and the allocation,
1571    /// because nothing does in `argon2_verify` either; on a login endpoint,
1572    /// where the string is whatever a database row (or a request) contained,
1573    /// that is a one-line denial of service. `t_cost` is the same story in CPU
1574    /// time rather than bytes.
1575    ///
1576    /// The plain entry points keep exact C parity and are the right choice when
1577    /// the string is trusted — a config file, a fixture, your own output. This
1578    /// one is for when it is not.
1579    ///
1580    /// ```
1581    /// use argon2_rust::{Algorithm, Argon2, Params, Version, params::Memory};
1582    ///
1583    /// let hostile = "$argon2id$v=19$m=4294967295,t=1,p=1$c29tZXNhbHQ$\
1584    ///                CTFhFdXPJO1aFaMaO6Mm5c8y7cJHAph8ArZWb2GRPPc";
1585    /// // 64 MiB, 8 passes, 4 lanes is far more than any sane stored hash.
1586    /// let ceiling = Params::builder().memory(Memory::mib(64)).passes(8).lanes(4).build()?;
1587    ///
1588    /// let err = Argon2::verify_encoded_bounded(
1589    ///     hostile, b"password", Algorithm::Argon2id, &ceiling,
1590    /// ).unwrap_err();
1591    /// // Rejected on the parameters, without ever asking for 4 TiB.
1592    /// assert_eq!(err, argon2_rust::Error::MemoryTooMuch);
1593    /// # Ok::<(), argon2_rust::Error>(())
1594    /// ```
1595    ///
1596    /// # What is bounded
1597    ///
1598    /// Both the cost *and* the allocation. The length of `encoded` is checked
1599    /// against the longest string `ceiling` could have produced — with
1600    /// [`BOUNDED_MAX_SALT_LEN`] allowed for the salt — **before** the decoder
1601    /// runs, because the decoder sizes its salt and tag buffers from the input.
1602    /// Then the decoded parameters are held to all four of the ceiling's
1603    /// numbers.
1604    ///
1605    /// # Worker threads
1606    ///
1607    /// `ceiling.threads()` bounds them, and it is a *fifth*, independent knob —
1608    /// none of the four checks above implies it. Decoding sets `threads = lanes`
1609    /// (C parity), so the string's own `p` would otherwise choose how many OS
1610    /// threads this call spawns. A ceiling that leaves
1611    /// [`ParamsBuilder::threads`](crate::params::ParamsBuilder::threads) unset
1612    /// has `threads == lanes` and so bounds them together; set it to allow wide
1613    /// strings without spawning wide:
1614    ///
1615    /// ```
1616    /// use argon2_rust::{Params, params::Memory};
1617    /// // Accept up to 256 lanes, but never run more than 2 workers.
1618    /// let ceiling = Params::builder()
1619    ///     .memory(Memory::mib(64))
1620    ///     .passes(8)
1621    ///     .lanes(256)
1622    ///     .threads(2)
1623    ///     .build()?;
1624    /// # Ok::<(), argon2_rust::Error>(())
1625    /// ```
1626    ///
1627    /// Clamping is always safe: `threads` is a scheduling knob that cannot
1628    /// change the tag — only `lanes` can — so a bounded verify accepts exactly
1629    /// the same strings whatever the budget.
1630    ///
1631    /// # Errors
1632    ///
1633    /// The errors of [`Argon2::verify_encoded`], plus — checked in this order,
1634    /// and reusing the C's own codes rather than inventing new ones —
1635    /// [`Error::DecodingLengthFail`] if `encoded` is longer than `ceiling` could
1636    /// have produced, [`Error::OutputTooLong`] if the decoded tag is longer than
1637    /// `ceiling.tag_len_bytes()`, [`Error::MemoryTooMuch`] if the decoded `m_cost`
1638    /// exceeds `ceiling.memory_kib()`, [`Error::TimeTooLarge`] if `t_cost` exceeds
1639    /// `ceiling.passes()`, and [`Error::LanesTooMany`] if `lanes` exceeds
1640    /// `ceiling.lanes()`.
1641    pub fn verify_encoded_bounded(
1642        encoded: &str,
1643        pwd: &[u8],
1644        algorithm: Algorithm,
1645        ceiling: &Params,
1646    ) -> Result<(), Error> {
1647        // argon2.c:260-262 `if (pwdlen > ARGON2_MAX_PWD_LENGTH)`.
1648        if pwd.len() > MAX_PWD_LENGTH as usize {
1649            return Err(Error::PwdTooLong);
1650        }
1651
1652        let decoded = decode_bounded(encoded, algorithm, ceiling)?;
1653
1654        Argon2::new(decoded.algorithm, decoded.version, decoded.params).verify(
1655            pwd,
1656            &decoded.salt,
1657            &decoded.hash,
1658        )
1659    }
1660
1661    /// [`Argon2::verify_encoded_with_ad`] with the cost ceiling of
1662    /// [`Argon2::verify_encoded_bounded`].
1663    ///
1664    /// A keyed deployment is *more* likely to be the one parsing untrusted
1665    /// strings, not less, so the bounded form exists for both.
1666    ///
1667    /// # Errors
1668    ///
1669    /// As [`Argon2::verify_encoded_bounded`], plus the secret/ad validation
1670    /// errors of [`Argon2::hash_into_with_ad`].
1671    pub fn verify_encoded_bounded_with_ad(
1672        encoded: &str,
1673        pwd: &[u8],
1674        secret: &[u8],
1675        ad: &[u8],
1676        algorithm: Algorithm,
1677        ceiling: &Params,
1678    ) -> Result<(), Error> {
1679        // argon2.c:260-262 `if (pwdlen > ARGON2_MAX_PWD_LENGTH)`.
1680        if pwd.len() > MAX_PWD_LENGTH as usize {
1681            return Err(Error::PwdTooLong);
1682        }
1683
1684        let decoded = decode_bounded(encoded, algorithm, ceiling)?;
1685
1686        let argon2 = Argon2::new(decoded.algorithm, decoded.version, decoded.params);
1687        let mut computed = try_zeroed_vec(argon2.params.tag_len_bytes())?;
1688        let result = argon2.hash_into_with_ad(pwd, &decoded.salt, secret, ad, &mut computed);
1689        let matched = result.is_ok() && constant_time_eq(&computed, &decoded.hash);
1690        clear_internal_memory(&mut computed);
1691
1692        result?;
1693        if matched {
1694            Ok(())
1695        } else {
1696            Err(Error::VerifyMismatch)
1697        }
1698    }
1699}
1700
1701/// Decode `encoded` and hold it to `ceiling`, for the `*_bounded` entry points.
1702///
1703/// # Why the length gate comes first
1704///
1705/// Checking the ceiling *after* decoding is not enough, and an earlier revision
1706/// of this function got that wrong. [`crate::encoding::decode_string`] sizes its
1707/// salt and tag buffers from the input string, so the decode itself is an
1708/// attacker-controlled allocation before any ceiling is consulted. Measured with
1709/// an allocator spy against the previous version: a well-formed string with
1710/// `m=8,t=1,p=1` and a 16 MiB Base64 tag peaked at **36 MiB** of live
1711/// allocation, then ran a full Argon2 and a 12 MiB comparison — under a ceiling
1712/// whose tag length was 32 bytes. Every cost was inside the ceiling; the tag was
1713/// never looked at.
1714///
1715/// So the size of the string is checked against what the ceiling could
1716/// legitimately produce *before* anything is parsed, and the decoded tag length
1717/// is then checked against `ceiling.tag_len_bytes()` as well. A ceiling is four
1718/// numbers, and all four now mean something.
1719fn decode_bounded(
1720    encoded: &str,
1721    algorithm: Algorithm,
1722    ceiling: &Params,
1723) -> Result<crate::encoding::Decoded, Error> {
1724    // The longest string the ceiling could have produced. `num_len` is monotone
1725    // in its argument and the costs are themselves capped below, so taking the
1726    // ceiling's own values gives a true upper bound. `encoded_len` counts the
1727    // C's NUL, so this is permissive by exactly one byte.
1728    let max_encoded = crate::encoding::encoded_len(
1729        algorithm,
1730        ceiling.passes(),
1731        ceiling.memory_kib(),
1732        ceiling.lanes(),
1733        BOUNDED_MAX_SALT_LEN,
1734        // The tag length is bounded by MAX_OUTLEN, so this cast cannot truncate.
1735        ceiling.tag_len_bytes() as u32,
1736    );
1737    if encoded.len() > max_encoded {
1738        // ARGON2_DECODING_LENGTH_FAIL: "Some of encoded parameters are too long
1739        // or too short". The C defines it for exactly this and never returns it;
1740        // it is the right code and it costs no new error variant.
1741        return Err(Error::DecodingLengthFail);
1742    }
1743
1744    let mut decoded = crate::encoding::decode_string(encoded, algorithm)?;
1745
1746    if decoded.params.tag_len_bytes() > ceiling.tag_len_bytes() {
1747        return Err(Error::OutputTooLong);
1748    }
1749    if decoded.params.memory_kib() > ceiling.memory_kib() {
1750        return Err(Error::MemoryTooMuch);
1751    }
1752    if decoded.params.passes() > ceiling.passes() {
1753        return Err(Error::TimeTooLarge);
1754    }
1755    if decoded.params.lanes() > ceiling.lanes() {
1756        return Err(Error::LanesTooMany);
1757    }
1758
1759    // The four checks above do **not** imply a worker-thread bound, and the
1760    // ceiling's `threads` is a separate field precisely so a caller can say
1761    // "allow wide strings, but never spawn wide". `decode_string` sets
1762    // `threads = lanes` (C parity, `argon2.c`), and `fill_pooled` spawns
1763    // `min(threads, lanes) - 1` helpers — so without this clamp a `p=256`
1764    // string inside a `lanes` ceiling of 256 spawns 255 OS threads even when
1765    // the ceiling asked for one worker. That is attacker-chosen concurrency on
1766    // an authentication path.
1767    //
1768    // Lowering `threads` is free: it is a pure scheduling knob that cannot
1769    // change the tag (only `lanes` can), which `threads_do_not_change_the_tag`
1770    // pins across both versions and all three algorithms.
1771    //
1772    // `min` with `lanes` keeps the value meaningful rather than merely legal —
1773    // workers above the lane count have nothing to claim — and cannot underflow
1774    // the `MIN_THREADS = 1` floor, because a validated ceiling has
1775    // `threads >= 1` and a decoded string has `lanes >= 1`.
1776    let threads = ceiling.threads().min(decoded.params.lanes());
1777    if threads != decoded.params.threads() {
1778        // `to_builder()` carries the four cost values and the tag length across
1779        // unchanged, so only the one field that actually moves is named here.
1780        // Re-listing all five through `Params::builder()` would invite exactly
1781        // the drift this clamp exists to prevent.
1782        decoded.params = decoded.params.to_builder().threads(threads).build()?;
1783    }
1784    Ok(decoded)
1785}
1786
1787// ---------------------------------------------------------------------------
1788// Hasher — the same API, over memory that survives the call
1789// ---------------------------------------------------------------------------
1790
1791/// An [`Argon2`] that keeps its block arena between calls.
1792///
1793/// Build one with [`Argon2::hasher`]. Every method mirrors the [`Argon2`]
1794/// method of the same name and returns the same bytes; the only difference is
1795/// that the arena is borrowed from a pool instead of allocated and freed each
1796/// time. Nothing else about the computation changes — same backend dispatch,
1797/// same threading, same wipe.
1798///
1799/// ```
1800/// use argon2_rust::{Algorithm, Argon2, Params, Version, params::Memory};
1801///
1802/// let params = Params::builder().memory(Memory::kib(1 << 8)).passes(1).build()?;
1803/// let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
1804/// let mut hasher = argon2.hasher();
1805///
1806/// let encoded = hasher.hash_encoded(b"password", b"somesalt")?;
1807/// assert!(hasher.verify_encoded(&encoded, b"password", Algorithm::Argon2id).is_ok());
1808/// # Ok::<(), argon2_rust::Error>(())
1809/// ```
1810///
1811/// # What it is worth, measured
1812///
1813/// Reuse skips the `mmap`, the first-touch page faults over the whole arena,
1814/// and the `munmap`. Interleaved A/B against [`Argon2::hash_into`], 15 paired
1815/// rounds on Linux/x86-64 (Sapphire Rapids, AVX-512):
1816///
1817/// ```text
1818///   m_cost   t   p |  one-shot |    pooled |  delta
1819///  ---------|-----|-----------|-----------|--------
1820///     8 KiB   1   1 |  20.4 us |   20.3 us |  -0.7%
1821///    64 KiB   1   1 |  27.9 us |   26.5 us |  -5.3%
1822///     1 MiB   1   1 |  212 us  |   185 us  | -11.7%
1823///     4 MiB   1   1 |  989 us  |   786 us  | -19.9%
1824///     4 MiB   1   4 |  806 us  |   592 us  | -26.9%
1825///    64 MiB   1   1 |  25.89 ms|  19.43 ms | -24.9%
1826///    64 MiB   1   4 |  11.65 ms|   8.40 ms | -34.0%
1827///   256 MiB   1   1 | 111.74 ms|  86.17 ms | -23.3%
1828///   256 MiB   1   4 |  46.14 ms|  35.09 ms | -24.0%
1829///   256 MiB   3   4 | 109.85 ms|  99.26 ms |  -9.7%
1830/// ```
1831///
1832/// The `t = 3` rows are smaller for the obvious reason: the same one-time
1833/// acquisition is spread over three passes of filling.
1834///
1835/// It does **not** remove allocator calls — there was only ever one per hash,
1836/// 1.7 us out of 306 ms at `m_cost = 1 GiB`.
1837///
1838/// # Wiping
1839///
1840/// Unchanged from the one-shot API. The arena is wiped when the call that
1841/// borrowed it returns — success, `?` error or unwind alike — so the window in
1842/// which a password's derived material is resident is exactly as long as it was
1843/// before. What reuse changes is that the wipe now doubles as the *next* call's
1844/// zeroing, instead of being followed by a fresh `alloc_zeroed` that zeroes
1845/// again.
1846///
1847/// Dropping the `Hasher` releases the arena to the allocator, wiped.
1848///
1849/// # Threading
1850///
1851/// One `Hasher` per thread. It is [`Send`], so it can move to whichever worker
1852/// picks up a request, and deliberately **not** [`Sync`]: two threads hashing
1853/// through one `Hasher` would be two hashes sharing one arena. The multi-lane
1854/// fill inside a single hash is unaffected — one [`std::thread::scope`] owns
1855/// its helper pool for the whole fill, over the arena this `Hasher` lent it for
1856/// the duration of that one call.
1857///
1858/// ```compile_fail
1859/// # use argon2_rust::{Algorithm, Argon2, Params, Version, params::Memory};
1860/// # let params = Params::builder().memory(Memory::kib(8)).passes(1).build().unwrap();
1861/// # let hasher = Argon2::new(Algorithm::Argon2id, Version::V0x13, params).hasher();
1862/// fn needs_sync<T: Sync>(_: &T) {}
1863/// needs_sync(&hasher);
1864/// ```
1865///
1866/// # Two spellings
1867///
1868/// Every alias mirrors [`Argon2`], trap included: [`Hasher::hash_password_into`]
1869/// writes a **raw** tag while [`Hasher::hash_password`] returns a **PHC
1870/// string**, because `_into` names a destination and not a format. See
1871/// [`Argon2`'s section of the same name](Argon2#two-spellings) for the table.
1872///
1873/// ```
1874/// use argon2_rust::{Algorithm, Argon2, Params, Version, params::Memory};
1875///
1876/// let params = Params::builder().memory(Memory::kib(1 << 8)).passes(1).build()?;
1877/// let mut hasher = Argon2::new(Algorithm::Argon2id, Version::V0x13, params).hasher();
1878///
1879/// // Same prefix, same arena, different return type and different format.
1880/// let mut raw = [0u8; 32];
1881/// hasher.hash_password_into(b"password", b"somesalt", &mut raw)?;
1882/// let phc = hasher.hash_password(b"password", b"somesalt")?;
1883///
1884/// assert!(phc.starts_with("$argon2id$v=19$m=256,t=1,p=1$c29tZXNhbHQ$"));
1885/// assert_eq!(hasher.hash(b"password", b"somesalt")?, raw);
1886/// # Ok::<(), argon2_rust::Error>(())
1887/// ```
1888pub struct Hasher {
1889    argon2: Argon2,
1890    workspace: Workspace,
1891}
1892
1893impl Hasher {
1894    /// The configuration this hasher applies.
1895    #[inline]
1896    #[must_use]
1897    pub const fn argon2(&self) -> &Argon2 {
1898        &self.argon2
1899    }
1900
1901    /// Point the hasher at a different configuration, keeping the memory.
1902    ///
1903    /// For a process that has to hash at more than one parameter set — a
1904    /// password migration, say. The arena grows if the new `m_cost` needs more
1905    /// blocks and is kept as-is if it needs fewer, so the steady state is one
1906    /// allocation sized to the largest configuration seen.
1907    #[inline]
1908    pub fn set_argon2(&mut self, argon2: Argon2) {
1909        self.argon2 = argon2;
1910    }
1911
1912    /// The configured algorithm.
1913    #[inline]
1914    #[must_use]
1915    pub const fn algorithm(&self) -> Algorithm {
1916        self.argon2.algorithm
1917    }
1918
1919    /// The configured version.
1920    #[inline]
1921    #[must_use]
1922    pub const fn version(&self) -> Version {
1923        self.argon2.version
1924    }
1925
1926    /// The configured parameters.
1927    #[inline]
1928    #[must_use]
1929    pub const fn params(&self) -> &Params {
1930        &self.argon2.params
1931    }
1932
1933    /// Allocate the arena now instead of during the first hash.
1934    ///
1935    /// Only moves the cost; it does not remove it. Worth doing when the first
1936    /// request must not be the slow one, or to find out at start-up rather than
1937    /// under load that `m_cost` does not fit in memory.
1938    ///
1939    /// # Errors
1940    ///
1941    /// [`Error::MemoryAllocationError`].
1942    pub fn reserve(&mut self) -> Result<(), Error> {
1943        self.workspace.reserve(self.argon2.params.memory_blocks() as usize)
1944    }
1945
1946    /// Blocks of arena the hasher is holding on to. 1 KiB each.
1947    ///
1948    /// 0 before the first hash, or after [`clear`](Hasher::clear). Diagnostic:
1949    /// it is how a test proves that reuse is actually happening.
1950    #[inline]
1951    #[must_use]
1952    pub fn reserved_blocks(&self) -> usize {
1953        self.workspace.capacity()
1954    }
1955
1956    /// Give the arena back to the allocator, wiped, and keep the configuration.
1957    ///
1958    /// For a worker going idle that would rather not sit on `m_cost` KiB. The
1959    /// next hash allocates again.
1960    pub fn clear(&mut self) {
1961        self.workspace.clear();
1962    }
1963
1964    /// Derive a tag into `out`. [`Argon2::hash_into`], reusing the arena.
1965    ///
1966    /// ```
1967    /// use argon2_rust::{Algorithm, Argon2, Params, Version, params::Memory};
1968    ///
1969    /// let params = Params::builder().memory(Memory::kib(64)).passes(1).build()?;
1970    /// let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
1971    /// let mut hasher = argon2.hasher();
1972    ///
1973    /// // `Argon2::hasher` allocates nothing; the first hash sizes the arena.
1974    /// assert_eq!(hasher.reserved_blocks(), 0);
1975    ///
1976    /// let mut tags = Vec::new();
1977    /// for pwd in [&b"first"[..], &b"second"[..]] {
1978    ///     let mut tag = [0u8; 32];
1979    ///     hasher.hash_into(pwd, b"somesalt", &mut tag)?;
1980    ///     tags.push(tag);
1981    /// }
1982    ///
1983    /// // Two hashes, one arena: 64 blocks of 1 KiB, the `m_cost` above. The
1984    /// // second call neither allocated nor grew it.
1985    /// assert_eq!(hasher.reserved_blocks(), 64);
1986    /// assert_ne!(tags[0], tags[1]);
1987    ///
1988    /// // Reuse changes where the memory came from and nothing else.
1989    /// assert_eq!(argon2.hash(b"second", b"somesalt")?, tags[1]);
1990    /// # Ok::<(), argon2_rust::Error>(())
1991    /// ```
1992    ///
1993    /// # Errors
1994    ///
1995    /// As [`Argon2::hash_into`].
1996    #[inline]
1997    pub fn hash_into(&mut self, pwd: &[u8], salt: &[u8], out: &mut [u8]) -> Result<(), Error> {
1998        self.hash_into_with_ad(pwd, salt, &[], &[], out)
1999    }
2000
2001    /// [`Argon2::hash_into_with_ad`], reusing the arena.
2002    ///
2003    /// # Errors
2004    ///
2005    /// As [`Argon2::hash_into`].
2006    pub fn hash_into_with_ad(
2007        &mut self,
2008        pwd: &[u8],
2009        salt: &[u8],
2010        secret: &[u8],
2011        ad: &[u8],
2012        out: &mut [u8],
2013    ) -> Result<(), Error> {
2014        let argon2 = self.argon2;
2015        self.hash_into_using(&argon2, pwd, salt, secret, ad, out)
2016    }
2017
2018    /// [`Argon2::hash`], reusing the arena.
2019    ///
2020    /// # Errors
2021    ///
2022    /// As [`Argon2::hash_into`].
2023    pub fn hash(&mut self, pwd: &[u8], salt: &[u8]) -> Result<Vec<u8>, Error> {
2024        let mut out = try_zeroed_vec(self.argon2.params.tag_len_bytes())?;
2025        self.hash_into(pwd, salt, &mut out)?;
2026        Ok(out)
2027    }
2028
2029    /// [`Argon2::hash_encoded`], reusing the arena.
2030    ///
2031    /// # Errors
2032    ///
2033    /// As [`Argon2::hash_into`], plus [`Error::EncodingFail`].
2034    pub fn hash_encoded(&mut self, pwd: &[u8], salt: &[u8]) -> Result<String, Error> {
2035        let argon2 = self.argon2;
2036        let mut tag = self.hash(pwd, salt)?;
2037        let encoded = crate::encoding::encode_string_alloc(
2038            argon2.algorithm,
2039            argon2.version,
2040            &argon2.params,
2041            salt,
2042            &tag,
2043        );
2044        // argon2.c:173 `clear_internal_memory(out, hashlen);`
2045        clear_internal_memory(&mut tag);
2046        encoded
2047    }
2048
2049    /// [`Argon2::verify`], reusing the arena.
2050    ///
2051    /// # Errors
2052    ///
2053    /// As [`Argon2::hash_into`], or [`Error::VerifyMismatch`].
2054    pub fn verify(&mut self, pwd: &[u8], salt: &[u8], expected: &[u8]) -> Result<(), Error> {
2055        let argon2 = self.argon2;
2056        self.verify_using(&argon2, pwd, salt, expected)
2057    }
2058
2059    /// [`Argon2::verify_encoded`], reusing the arena.
2060    ///
2061    /// The parameters come from `encoded`, **not** from this hasher — that is
2062    /// what verifying a stored PHC string means, and it is what lets one hasher
2063    /// check strings written at several different `m_cost`s.
2064    ///
2065    /// ```
2066    /// use argon2_rust::{Algorithm, Argon2, Error, Params, Version, params::Memory};
2067    ///
2068    /// let params = Params::builder().memory(Memory::kib(64)).passes(1).build()?;
2069    /// let mut hasher = Argon2::new(Algorithm::Argon2id, Version::V0x13, params).hasher();
2070    ///
2071    /// // Registration: one string, carrying the salt and the parameters.
2072    /// let stored = hasher.hash_encoded(b"password", b"somesalt")?;
2073    /// assert_eq!(
2074    ///     stored,
2075    ///     "$argon2id$v=19$m=64,t=1,p=1$c29tZXNhbHQ$cpx6VEQbwTVZvcpxNIxOVUWZ5xnAipUmAe1cg2GMG70",
2076    /// );
2077    ///
2078    /// // Two logins, over the arena the registration already paid for.
2079    /// assert_eq!(
2080    ///     hasher.verify_encoded(&stored, b"password", Algorithm::Argon2id),
2081    ///     Ok(()),
2082    /// );
2083    /// assert_eq!(
2084    ///     hasher.verify_encoded(&stored, b"wrong", Algorithm::Argon2id),
2085    ///     Err(Error::VerifyMismatch),
2086    /// );
2087    ///
2088    /// // The string's `m=64` is not above what this hasher already holds, so
2089    /// // the pool served both verifies and did not grow. See below for what
2090    /// // happens when a decoded `m_cost` is larger.
2091    /// assert_eq!(hasher.reserved_blocks(), 64);
2092    /// # Ok::<(), argon2_rust::Error>(())
2093    /// ```
2094    ///
2095    /// # The string cannot grow this hasher — but it can still be huge
2096    ///
2097    /// Read this one first: what follows bounds what an untrusted `m_cost` can
2098    /// **retain**, and nothing at all about what it can **allocate**. A decoded
2099    /// `m_cost` of `0xFFFFFFFF` still asks for a 4 TiB arena here, exactly as it
2100    /// does in [`Argon2::verify_encoded`] and exactly as it does in the C. If
2101    /// `encoded` comes from anywhere an attacker can write, bound it first —
2102    /// [`Hasher::verify_encoded_bounded`] does that — or the process dies on the
2103    /// allocation regardless of everything below.
2104    ///
2105    /// `encoded` is untrusted input: on a login endpoint it is whatever the
2106    /// database row said, and a `m_cost` field is four bytes of decimal that can
2107    /// ask for 4 TiB. A pooled arena is *retained*, so if a decoded `m_cost`
2108    /// were allowed to size it, one string would set a permanent high-water mark
2109    /// on a long-lived per-worker hasher — memory the process never gives back,
2110    /// chosen by the caller rather than by this hasher's owner.
2111    ///
2112    /// So it is not allowed to. A decoded `m_cost` that fits in memory this
2113    /// hasher already holds — [`reserved_blocks`](Hasher::reserved_blocks), or
2114    /// the [`params`](Hasher::params) it is configured for — is served from the
2115    /// pool as usual. One that would have to *grow* the pool gets a private
2116    /// arena instead, allocated, wiped and freed inside this call exactly as
2117    /// [`Argon2::verify_encoded`] does. Verifying still works at any `m_cost`
2118    /// the decoder accepts — including ones that will not fit in this machine.
2119    /// It just cannot leave anything behind.
2120    ///
2121    /// That mirrors the C, where `finalize()` ends every `argon2_ctx` with
2122    /// `free_memory(...)` (`core.c:184`), so `argon2_verify` never retains an
2123    /// arena sized by the string it was handed.
2124    ///
2125    /// To verify *and* keep the memory — a migration that re-hashes upward, say
2126    /// — call [`set_argon2`](Hasher::set_argon2) first. Then the size is the
2127    /// owner's choice, which is the whole distinction being drawn here.
2128    ///
2129    /// # Errors
2130    ///
2131    /// As [`Argon2::verify_encoded`].
2132    pub fn verify_encoded(
2133        &mut self,
2134        encoded: &str,
2135        pwd: &[u8],
2136        algorithm: Algorithm,
2137    ) -> Result<(), Error> {
2138        // argon2.c:260-262 `if (pwdlen > ARGON2_MAX_PWD_LENGTH)`.
2139        if pwd.len() > MAX_PWD_LENGTH as usize {
2140            return Err(Error::PwdTooLong);
2141        }
2142
2143        // argon2.c:289 `decode_string(&ctx, encoded, type)`.
2144        let decoded = crate::encoding::decode_string(encoded, algorithm)?;
2145
2146        // argon2.c:302 `argon2_verify_ctx(&ctx, desired_result, type)`.
2147        let argon2 = Argon2::new(decoded.algorithm, decoded.version, decoded.params);
2148
2149        if decoded.params.memory_blocks() as usize > self.pooled_ceiling() {
2150            // Bigger than any arena this hasher's *owner* asked for. Run it on a
2151            // private arena that is freed on the way out, so an attacker-chosen
2152            // `m_cost` cannot pin memory to a worker for the rest of its life.
2153            return argon2.verify(pwd, &decoded.salt, &decoded.hash);
2154        }
2155        self.verify_using(&argon2, pwd, &decoded.salt, &decoded.hash)
2156    }
2157
2158    /// [`Argon2::verify_encoded_with_ad`], reusing the arena.
2159    ///
2160    /// # Errors
2161    ///
2162    /// As [`Argon2::verify_encoded_with_ad`].
2163    pub fn verify_encoded_with_ad(
2164        &mut self,
2165        encoded: &str,
2166        pwd: &[u8],
2167        secret: &[u8],
2168        ad: &[u8],
2169        algorithm: Algorithm,
2170    ) -> Result<(), Error> {
2171        // argon2.c:260-262 `if (pwdlen > ARGON2_MAX_PWD_LENGTH)`.
2172        if pwd.len() > MAX_PWD_LENGTH as usize {
2173            return Err(Error::PwdTooLong);
2174        }
2175
2176        // argon2.c:289 `decode_string(&ctx, encoded, type)`.
2177        let decoded = crate::encoding::decode_string(encoded, algorithm)?;
2178
2179        // argon2.c:302 `argon2_verify_ctx(&ctx, desired_result, type)`.
2180        let argon2 = Argon2::new(decoded.algorithm, decoded.version, decoded.params);
2181
2182        if decoded.params.memory_blocks() as usize > self.pooled_ceiling() {
2183            // As `verify_encoded`: keep an attacker-chosen `m_cost` off the
2184            // pooled arena by running on a one-shot arena instead.
2185            let mut computed = try_zeroed_vec(argon2.params.tag_len_bytes())?;
2186            let result =
2187                argon2.hash_into_with_ad(pwd, &decoded.salt, secret, ad, &mut computed);
2188            let matched = result.is_ok() && constant_time_eq(&computed, &decoded.hash);
2189            clear_internal_memory(&mut computed);
2190            result?;
2191            return if matched {
2192                Ok(())
2193            } else {
2194                Err(Error::VerifyMismatch)
2195            };
2196        }
2197        self.verify_using_ad(&argon2, pwd, &decoded.salt, secret, ad, &decoded.hash)
2198    }
2199
2200    // -----------------------------------------------------------------
2201    // Password-flavoured spellings, matching `Argon2`'s
2202    // -----------------------------------------------------------------
2203
2204    /// Derive a **raw** tag into `out`, not a PHC string, reusing the arena.
2205    ///
2206    /// [`Argon2::hash_password_into`] over pooled memory, which is the same
2207    /// function as [`Hasher::hash_into`]. `out.len()` must equal
2208    /// [`Params::tag_len_bytes`]. For the PHC string, [`Hasher::hash_password`].
2209    ///
2210    /// # Errors
2211    ///
2212    /// As [`Argon2::hash_into`].
2213    #[inline]
2214    pub fn hash_password_into(
2215        &mut self,
2216        pwd: &[u8],
2217        salt: &[u8],
2218        out: &mut [u8],
2219    ) -> Result<(), Error> {
2220        self.hash_into(pwd, salt, out)
2221    }
2222
2223    /// Derive a tag and return the **PHC string** for it, reusing the arena.
2224    ///
2225    /// [`Argon2::hash_password`] over pooled memory, which is the same function
2226    /// as [`Hasher::hash_encoded`]. For the raw tag instead, its sibling
2227    /// [`Hasher::hash_password_into`] or [`Hasher::hash`].
2228    ///
2229    /// # Errors
2230    ///
2231    /// As [`Argon2::hash_into`], plus [`Error::EncodingFail`].
2232    #[inline]
2233    pub fn hash_password(&mut self, pwd: &[u8], salt: &[u8]) -> Result<String, Error> {
2234        self.hash_encoded(pwd, salt)
2235    }
2236
2237    /// Derive a **PHC string** with a fresh salt from the OS entropy source,
2238    /// reusing the arena.
2239    ///
2240    /// [`Argon2::hash_password_with_random_salt`] over pooled memory, which is
2241    /// [`Hasher::hash_encoded`] with a [`RANDOM_SALT_LEN`]-byte salt drawn for
2242    /// you and carried in the returned string. There is no raw-tag counterpart:
2243    /// a caller who keeps the tag has to keep the salt too, and then generating
2244    /// it here saves nothing.
2245    ///
2246    /// This is the spelling that matters for the case the type exists to serve:
2247    /// a long-lived per-worker hasher registering many users, where every hash
2248    /// wants both the pooled arena *and* a fresh salt.
2249    ///
2250    /// # Errors
2251    ///
2252    /// [`Error::OsRandom`] if every OS entropy source fails, plus the errors of
2253    /// [`Hasher::hash_encoded`].
2254    #[cfg(feature = "std")]
2255    pub fn hash_password_with_random_salt(&mut self, pwd: &[u8]) -> Result<String, Error> {
2256        // Not wiped on the way out, deliberately, and unlike every other
2257        // buffer in this file: the salt is *published* in the returned string,
2258        // so scrubbing the stack copy protects nothing that is not already in
2259        // the caller's hands. `clear_internal_memory` is for secret-derived
2260        // material; a salt is not that.
2261        let mut salt = [0u8; RANDOM_SALT_LEN];
2262        crate::random::os_random(&mut salt)?;
2263        self.hash_encoded(pwd, &salt)
2264    }
2265
2266    /// Check `pwd` against a **PHC string**, not a raw tag, reusing the arena.
2267    ///
2268    /// [`Argon2::verify_password`] over pooled memory, which is the same
2269    /// function as [`Hasher::verify_encoded`] and inherits its pooled-arena
2270    /// rule: a decoded `m_cost` above this hasher's high-water mark runs on a
2271    /// private arena that is freed on the way out, so the string cannot grow
2272    /// the pool. To check a raw expected tag instead, [`Hasher::verify`].
2273    ///
2274    /// # Errors
2275    ///
2276    /// As [`Argon2::verify_encoded`].
2277    #[inline]
2278    pub fn verify_password(
2279        &mut self,
2280        encoded: &str,
2281        pwd: &[u8],
2282        algorithm: Algorithm,
2283    ) -> Result<(), Error> {
2284        self.verify_encoded(encoded, pwd, algorithm)
2285    }
2286
2287    /// [`Argon2::verify_encoded_bounded`], reusing the arena.
2288    ///
2289    /// The ceiling is checked before anything is allocated, so it bounds the
2290    /// *allocation* — which is the half [`Hasher::verify_encoded`] does not
2291    /// address. Note that the pooled-arena rule still applies underneath: a
2292    /// decoded `m_cost` within `ceiling` but above this hasher's own high-water
2293    /// mark runs on a private arena, so passing a generous `ceiling` cannot
2294    /// enlarge the pool either.
2295    ///
2296    /// `ceiling.threads()` bounds the worker threads exactly as it does on
2297    /// [`Argon2::verify_encoded_bounded`] — worth knowing here in particular,
2298    /// since a `Hasher` is what a server holds while verifying strings it did
2299    /// not write, and the arena it reuses is not the only resource a wide `p`
2300    /// can spend.
2301    ///
2302    /// # Errors
2303    ///
2304    /// As [`Argon2::verify_encoded_bounded`].
2305    pub fn verify_encoded_bounded(
2306        &mut self,
2307        encoded: &str,
2308        pwd: &[u8],
2309        algorithm: Algorithm,
2310        ceiling: &Params,
2311    ) -> Result<(), Error> {
2312        // argon2.c:260-262 `if (pwdlen > ARGON2_MAX_PWD_LENGTH)`.
2313        if pwd.len() > MAX_PWD_LENGTH as usize {
2314            return Err(Error::PwdTooLong);
2315        }
2316
2317        let decoded = decode_bounded(encoded, algorithm, ceiling)?;
2318
2319        let argon2 = Argon2::new(decoded.algorithm, decoded.version, decoded.params);
2320        if decoded.params.memory_blocks() as usize > self.pooled_ceiling() {
2321            // As `verify_encoded`: keep an m_cost this hasher's owner never
2322            // asked for off the retained arena.
2323            return argon2.verify(pwd, &decoded.salt, &decoded.hash);
2324        }
2325        self.verify_using(&argon2, pwd, &decoded.salt, &decoded.hash)
2326    }
2327
2328    /// [`Argon2::verify_encoded_bounded_with_ad`], reusing the arena.
2329    ///
2330    /// # Errors
2331    ///
2332    /// As [`Argon2::verify_encoded_bounded_with_ad`].
2333    pub fn verify_encoded_bounded_with_ad(
2334        &mut self,
2335        encoded: &str,
2336        pwd: &[u8],
2337        secret: &[u8],
2338        ad: &[u8],
2339        algorithm: Algorithm,
2340        ceiling: &Params,
2341    ) -> Result<(), Error> {
2342        // argon2.c:260-262 `if (pwdlen > ARGON2_MAX_PWD_LENGTH)`.
2343        if pwd.len() > MAX_PWD_LENGTH as usize {
2344            return Err(Error::PwdTooLong);
2345        }
2346
2347        let decoded = decode_bounded(encoded, algorithm, ceiling)?;
2348
2349        let argon2 = Argon2::new(decoded.algorithm, decoded.version, decoded.params);
2350        if decoded.params.memory_blocks() as usize > self.pooled_ceiling() {
2351            // As `verify_encoded_with_ad`: an m_cost this hasher's owner never
2352            // asked for runs on a one-shot arena.
2353            let mut computed = try_zeroed_vec(argon2.params.tag_len_bytes())?;
2354            let result = argon2.hash_into_with_ad(pwd, &decoded.salt, secret, ad, &mut computed);
2355            let matched = result.is_ok() && constant_time_eq(&computed, &decoded.hash);
2356            clear_internal_memory(&mut computed);
2357            result?;
2358            return if matched {
2359                Ok(())
2360            } else {
2361                Err(Error::VerifyMismatch)
2362            };
2363        }
2364        self.verify_using_ad(&argon2, pwd, &decoded.salt, secret, ad, &decoded.hash)
2365    }
2366
2367    // -----------------------------------------------------------------
2368    // The two private workers every public method above funnels through
2369    // -----------------------------------------------------------------
2370
2371    /// The largest arena an *untrusted* `m_cost` may borrow from the pool.
2372    ///
2373    /// Two sources, both chosen by whoever owns this hasher, never by an input:
2374    /// the configuration it was built or [`set_argon2`](Hasher::set_argon2)'d
2375    /// with, and whatever the workspace already holds (which
2376    /// [`reserve`](Hasher::reserve) or an earlier, larger configuration may have
2377    /// made bigger than the current one).
2378    ///
2379    /// The guarantee is a ceiling, not a freeze: a decoded `m_cost` under this
2380    /// bound may still be the thing that allocates the arena, on a hasher whose
2381    /// owner has not hashed yet. What it cannot do is push the retained arena
2382    /// past a size the owner has already asked for — so the worst an input can
2383    /// cost is memory the very next `hash_into` was going to take anyway, and
2384    /// there is no ratchet.
2385    ///
2386    /// The one caller is [`verify_encoded`](Hasher::verify_encoded), because it
2387    /// is the only method whose `m_cost` does not come from `self`.
2388    #[inline]
2389    fn pooled_ceiling(&self) -> usize {
2390        core::cmp::max(
2391            self.workspace.capacity(),
2392            self.argon2.params.memory_blocks() as usize,
2393        )
2394    }
2395
2396    /// `argon2_ctx()` with `argon2`'s configuration and this hasher's memory.
2397    ///
2398    /// `argon2` is passed explicitly rather than read from `self` so that
2399    /// [`verify_encoded`](Hasher::verify_encoded) can use the parameters it
2400    /// decoded from the string. It is [`Copy`], so callers hand in a copy and
2401    /// the borrow checker never has to reconcile it with `&mut self.workspace`.
2402    fn hash_into_using(
2403        &mut self,
2404        argon2: &Argon2,
2405        pwd: &[u8],
2406        salt: &[u8],
2407        secret: &[u8],
2408        ad: &[u8],
2409        out: &mut [u8],
2410    ) -> Result<(), Error> {
2411        // SAFETY: the same argument that makes `Argon2::hash_into_with_ad`
2412        // safe — the only `Backend` this crate's safe API ever names is
2413        // `fill_block::backend()`, the cached result of the
2414        // `is_*_feature_detected!` cascade, so this CPU can execute it by
2415        // construction.
2416        unsafe {
2417            hash_in_workspace(
2418                &mut self.workspace,
2419                crate::fill_block::backend(),
2420                argon2.algorithm,
2421                argon2.version,
2422                &argon2.params,
2423                pwd,
2424                salt,
2425                secret,
2426                ad,
2427                out,
2428                None,
2429                None,
2430            )
2431        }
2432    }
2433
2434    /// [`Argon2::verify`]'s body, over this hasher's memory.
2435    fn verify_using(
2436        &mut self,
2437        argon2: &Argon2,
2438        pwd: &[u8],
2439        salt: &[u8],
2440        expected: &[u8],
2441    ) -> Result<(), Error> {
2442        self.verify_using_ad(argon2, pwd, salt, &[], &[], expected)
2443    }
2444
2445    fn verify_using_ad(
2446        &mut self,
2447        argon2: &Argon2,
2448        pwd: &[u8],
2449        salt: &[u8],
2450        secret: &[u8],
2451        ad: &[u8],
2452        expected: &[u8],
2453    ) -> Result<(), Error> {
2454        let mut computed = try_zeroed_vec(argon2.params.tag_len_bytes())?;
2455        let result = self.hash_into_using(argon2, pwd, salt, secret, ad, &mut computed);
2456        // argon2.c:349 `argon2_compare(hash, context->out, context->outlen)`.
2457        let matched = result.is_ok() && constant_time_eq(&computed, expected);
2458        clear_internal_memory(&mut computed);
2459
2460        result?;
2461        if matched {
2462            Ok(())
2463        } else {
2464            Err(Error::VerifyMismatch)
2465        }
2466    }
2467}
2468
2469impl core::fmt::Debug for Hasher {
2470    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2471        f.debug_struct("Hasher")
2472            .field("argon2", &self.argon2)
2473            .field("reserved_blocks", &self.reserved_blocks())
2474            .finish()
2475    }
2476}
2477
2478/// A zeroed `Vec<u8>` of `len` bytes, without the abort-on-OOM of
2479/// `Vec::with_capacity`.
2480fn try_zeroed_vec(len: usize) -> Result<Vec<u8>, Error> {
2481    let mut v = Vec::new();
2482    v.try_reserve(len)
2483        .map_err(|_| Error::MemoryAllocationError)?;
2484    // Cannot reallocate: the capacity was just reserved.
2485    v.resize(len, 0);
2486    Ok(v)
2487}
2488
2489/// `argon2_ctx()`: validate, size the arena, initialise, fill, finalise.
2490///
2491/// The one place the whole computation lives; every public entry point funnels
2492/// through here. `backend` is resolved by the caller so the forced-backend test
2493/// hook and the normal path share this body.
2494///
2495/// # Safety
2496///
2497/// As [`fill_memory_blocks_traced`]: this CPU must be able to execute `backend`.
2498// One parameter per `argon2_context` field this port needs; collapsing them into
2499// a struct would just move the same list somewhere else.
2500#[allow(clippy::too_many_arguments)]
2501unsafe fn hash_inner(
2502    backend: Backend,
2503    algorithm: Algorithm,
2504    version: Version,
2505    params: &Params,
2506    pwd: &[u8],
2507    salt: &[u8],
2508    secret: &[u8],
2509    ad: &[u8],
2510    out: &mut [u8],
2511) -> Result<(), Error> {
2512    // SAFETY: forwarded verbatim from this function's own contract.
2513    unsafe {
2514        hash_owned(
2515            backend, algorithm, version, params, pwd, salt, secret, ad, out, None, None,
2516        )
2517    }
2518}
2519
2520/// One-shot hashing over a freshly allocated arena.
2521///
2522/// `h0_out` is `None` on every stable API path. That distinction is
2523/// security-relevant: normal hashing must not materialise a second copy of H0
2524/// merely to throw it away after the computation. The unstable KAT hook passes
2525/// a destination because H0 is one of its requested outputs.
2526///
2527/// # Safety
2528///
2529/// As [`fill_memory_blocks_traced`]: this CPU must be able to execute `backend`.
2530#[allow(clippy::too_many_arguments)]
2531unsafe fn hash_owned(
2532    backend: Backend,
2533    algorithm: Algorithm,
2534    version: Version,
2535    params: &Params,
2536    pwd: &[u8],
2537    salt: &[u8],
2538    secret: &[u8],
2539    ad: &[u8],
2540    out: &mut [u8],
2541    trace: Option<PassTrace<'_>>,
2542    h0_out: Option<&mut [u8; PREHASH_DIGEST_LENGTH]>,
2543) -> Result<(), Error> {
2544    let memory_blocks = validate_and_size(params, pwd, salt, secret, ad, out)?;
2545
2546    // core.c:621 "1. Memory allocation". A fresh allocation every call, freed
2547    // on the way out. `Hasher` runs the same computation over an arena borrowed
2548    // from a `Workspace`.
2549    let mut arena = Arena::new(memory_blocks)?;
2550
2551    // SAFETY: `backend` is forwarded verbatim from this function's own
2552    // contract. `arena` was just sized from the same `params`, and it lives
2553    // until the end of this function, i.e. past every use inside.
2554    unsafe {
2555        hash_in_arena(
2556            &mut arena, backend, algorithm, version, params, pwd, salt, secret, ad, out, trace,
2557            h0_out,
2558        )
2559    }
2560    // `arena` drops here: `Arena::drop` wipes it (`zeroize-memory`) and frees
2561    // it, which is core.c:184's `free_memory(...)`. It drops on `Ok`, `Err` and
2562    // unwind alike. The two `?`s above fire before the arena exists.
2563}
2564
2565/// `argon2_ctx()` with the two hooks `src/genkat.c` needs.
2566///
2567/// Returns the 64-byte pre-hashing digest `H0` that `initial_kat()` prints, and
2568/// invokes `trace(pass, whole_arena)` after each pass, which is what
2569/// `internal_kat()` prints. `tests/kat.rs` reaches this through `__internal`.
2570///
2571/// # Safety
2572///
2573/// As [`fill_memory_blocks_traced`]: this CPU must be able to execute `backend`,
2574/// which `backend.is_available()` or [`crate::fill_block::backend`] establishes.
2575/// Nothing else here is unsafe — validation, allocation and finalisation are all
2576/// ordinary safe code — but a `Backend` this CPU lacks makes the fill loop jump
2577/// into a `#[target_feature]` function it cannot run.
2578///
2579/// # Errors
2580///
2581/// As [`Argon2::hash_into`].
2582#[allow(clippy::too_many_arguments)]
2583pub unsafe fn hash_traced(
2584    backend: Backend,
2585    algorithm: Algorithm,
2586    version: Version,
2587    params: &Params,
2588    pwd: &[u8],
2589    salt: &[u8],
2590    secret: &[u8],
2591    ad: &[u8],
2592    out: &mut [u8],
2593    trace: Option<PassTrace<'_>>,
2594) -> Result<[u8; PREHASH_DIGEST_LENGTH], Error> {
2595    let mut h0 = [0u8; PREHASH_DIGEST_LENGTH];
2596    // SAFETY: forwarded verbatim from this function's own contract.
2597    let result = unsafe {
2598        hash_owned(
2599            backend,
2600            algorithm,
2601            version,
2602            params,
2603            pwd,
2604            salt,
2605            secret,
2606            ad,
2607            out,
2608            trace,
2609            Some(&mut h0),
2610        )
2611    };
2612    if let Err(error) = result {
2613        // H0 was requested as output, but an error means it will not leave this
2614        // function. Do not turn that failed internal trace into stack residue.
2615        clear_internal_memory(&mut h0);
2616        return Err(error);
2617    }
2618    Ok(h0)
2619}
2620
2621/// Steps 1 and 2 of `argon2_ctx()`: validate every input, then align the memory
2622/// size. Returns the block count the arena must have.
2623///
2624/// Split out so that both arena sources — [`hash_traced`]'s one-shot
2625/// [`Arena::new`] and [`Hasher`]'s pooled [`Workspace`] — reject bad input
2626/// *before* anything is allocated, and reject it identically.
2627///
2628/// # Errors
2629///
2630/// Whatever [`Params::validate_for`] returns, or [`Error::OutPtrMismatch`].
2631fn validate_and_size(
2632    params: &Params,
2633    pwd: &[u8],
2634    salt: &[u8],
2635    secret: &[u8],
2636    ad: &[u8],
2637    out: &[u8],
2638) -> Result<usize, Error> {
2639    // argon2.c:41 "1. Validate all inputs".
2640    params.validate_for(pwd.len(), salt.len(), secret.len(), ad.len())?;
2641
2642    // argon2.c:49-51 `ARGON2_INCORRECT_TYPE` cannot fire: `Algorithm` is a
2643    // closed enum, so there is no "no such version of Argon2".
2644    //
2645    // Rust-only check. The C's `context->out` and `context->outlen` are one
2646    // object; here the buffer and the configured length are separate, so they
2647    // can disagree. `ARGON2_OUT_PTR_MISMATCH` is defined in `argon2.h` but
2648    // never returned by the C, which makes it exactly the right code for this.
2649    if out.len() != params.tag_len_bytes() {
2650        return Err(Error::OutPtrMismatch);
2651    }
2652
2653    // argon2.c:55-70 "2. Align memory size". See `Params::memory_layout`.
2654    Ok(params.memory_layout().0 as usize)
2655}
2656
2657/// Steps 3 to 5 of `argon2_ctx()` over an arena the caller already sized.
2658///
2659/// The whole computation lives here — pre-hash, first blocks, fill, finalise —
2660/// so the one-shot and pooled paths cannot drift apart. Everything they do not
2661/// share is on either side of this call: where the arena came from, and what
2662/// happens to it afterwards.
2663///
2664/// Deliberately does **not** zero the arena. Argon2 does not need it (pass 0
2665/// writes every block before anything reads one) and [`Arena`] already
2666/// guarantees the only property that matters for soundness, which is that every
2667/// block is *initialised*. See the module docs on [`crate::memory`].
2668///
2669/// # Safety
2670///
2671/// As [`fill_memory_blocks_traced`]: this CPU must be able to execute `backend`.
2672///
2673/// # Errors
2674///
2675/// [`Error::MemoryAllocationError`] if `arena.len()` disagrees with
2676/// `params.memory_layout()`, plus whatever [`initial_hash`],
2677/// [`fill_first_blocks`], [`fill_memory_blocks_traced`] and [`finalize`] return.
2678#[allow(clippy::too_many_arguments)]
2679unsafe fn hash_in_arena(
2680    arena: &mut Arena,
2681    backend: Backend,
2682    algorithm: Algorithm,
2683    version: Version,
2684    params: &Params,
2685    pwd: &[u8],
2686    salt: &[u8],
2687    secret: &[u8],
2688    ad: &[u8],
2689    out: &mut [u8],
2690    trace: Option<PassTrace<'_>>,
2691    h0_out: Option<&mut [u8; PREHASH_DIGEST_LENGTH]>,
2692) -> Result<(), Error> {
2693    let (memory_blocks, _segment_length, lane_length) = params.memory_layout();
2694
2695    // `Instance::new`'s safety contract is `memory_len == memory_blocks`, and
2696    // below it is handed `arena.len()`. Both callers size the arena from this
2697    // same `params`, so this can only fire if someone wires up a third one
2698    // wrongly — at which point it must be an error, not undefined behaviour.
2699    // A pooled arena whose *capacity* is larger is fine and expected; it is the
2700    // visible `len()` that has to match.
2701    if arena.len() != memory_blocks as usize {
2702        return Err(Error::MemoryAllocationError);
2703    }
2704
2705    // The release wipe may use as many threads as the caller sanctioned. It
2706    // cannot affect the tag, so `threads()` — the OS-thread budget — is the
2707    // right number here rather than `effective_threads()`, which is
2708    // `min(threads, lanes)` and describes the *algorithmic* parallelism.
2709    arena.set_workers(params.threads());
2710
2711    // core.c:631 "2. Initial hashing". The 8 bytes after `H0` are already zero,
2712    // which is what core.c:633 achieves with `clear_internal_memory`.
2713    let mut blockhash = [0u8; PREHASH_SEED_LENGTH];
2714    if let Err(error) = initial_hash_into(
2715        algorithm,
2716        version,
2717        params,
2718        pwd,
2719        salt,
2720        secret,
2721        ad,
2722        &mut blockhash,
2723    ) {
2724        clear_internal_memory(&mut blockhash);
2725        return Err(error);
2726    }
2727    if let Some(h0) = h0_out {
2728        #[cfg(all(test, feature = "std"))]
2729        H0_COPY_COUNT.with(|count| count.set(count.get() + 1));
2730        h0.copy_from_slice(&blockhash[..PREHASH_DIGEST_LENGTH]);
2731    }
2732
2733    // core.c:643 "3. Creating first blocks".
2734    let fill_first = fill_first_blocks(
2735        &mut blockhash,
2736        arena.as_mut_slice(),
2737        params.lanes(),
2738        lane_length,
2739    );
2740    // core.c:645 `clear_internal_memory(blockhash, ARGON2_PREHASH_SEED_LENGTH);`
2741    clear_internal_memory(&mut blockhash);
2742    fill_first?;
2743
2744    // SAFETY: `arena` is borrowed for the whole of this function and `instance`
2745    // does not escape it, so the arena outlives every use of the pointer. It
2746    // owns `arena.len()` initialised, `ARENA_ALIGN`-aligned `Block`s — that is
2747    // `Arena`'s invariant 1, and it holds for a pooled arena exactly as it does
2748    // for a fresh one, since neither reuse nor the release wipe can
2749    // de-initialise memory. `arena.len() == memory_blocks` was just checked,
2750    // which is `Instance::new`'s remaining requirement.
2751    let instance =
2752        unsafe { Instance::new(arena.as_mut_ptr(), arena.len(), algorithm, version, params) };
2753
2754    // argon2.c:89 "4. Filling memory".
2755    // SAFETY: the CPU's ability to execute `backend` is forwarded verbatim from
2756    // this function's own contract. `instance` was just built from an `Arena`
2757    // that outlives it, and the arena is uniquely borrowed (`&mut Arena`), so no
2758    // other thread holds a handle on it.
2759    unsafe { fill_memory_blocks_traced(&instance, backend, trace) }?;
2760
2761    // argon2.c:95 "5. Finalization". Wiping and releasing the arena is the
2762    // caller's job, and it happens on this function's error paths too because
2763    // both callers do it in a `Drop`.
2764    finalize(&instance, out)?;
2765
2766    Ok(())
2767}
2768
2769/// [`hash_traced`] over an arena borrowed from `workspace` instead of a fresh
2770/// one. The engine behind every [`Hasher`] method.
2771///
2772/// # Safety
2773///
2774/// As [`fill_memory_blocks_traced`]: this CPU must be able to execute `backend`.
2775///
2776/// # Errors
2777///
2778/// As [`hash_traced`].
2779#[allow(clippy::too_many_arguments)]
2780unsafe fn hash_in_workspace(
2781    workspace: &mut Workspace,
2782    backend: Backend,
2783    algorithm: Algorithm,
2784    version: Version,
2785    params: &Params,
2786    pwd: &[u8],
2787    salt: &[u8],
2788    secret: &[u8],
2789    ad: &[u8],
2790    out: &mut [u8],
2791    trace: Option<PassTrace<'_>>,
2792    h0_out: Option<&mut [u8; PREHASH_DIGEST_LENGTH]>,
2793) -> Result<(), Error> {
2794    let memory_blocks = validate_and_size(params, pwd, salt, secret, ad, out)?;
2795
2796    // The whole point: no allocator call and no zeroing memset when the parked
2797    // arena is already big enough. `acquire` only reallocates when it has to
2798    // grow, and the previous release left the blocks zeroed.
2799    let mut arena = workspace.acquire(memory_blocks)?;
2800
2801    // SAFETY: `backend` is forwarded verbatim from this function's own
2802    // contract, and `arena` was just sized from the same `params`.
2803    unsafe {
2804        hash_in_arena(
2805            &mut arena, backend, algorithm, version, params, pwd, salt, secret, ad, out, trace,
2806            h0_out,
2807        )
2808    }
2809    // The `ArenaGuard` drops here and hands the arena back to `workspace` after
2810    // a `clear_internal_memory_blocks` over exactly the blocks this hash could
2811    // reach. It drops whether the call above returned `Ok` or `Err`, and on
2812    // unwind — that is the reason to take a guard rather than an owned `Arena`.
2813    // Same wipe as `Arena::drop`, same `zeroize-memory` gate, just before the
2814    // free instead of together with it. The next acquisition therefore starts
2815    // from a zeroed arena without a second memset, and that saved memset is the
2816    // entire measured win. The two `?`s above fire before the guard exists.
2817}
2818
2819/// Run a hash with a specific [`Backend`], bypassing runtime detection.
2820///
2821/// Test and bench hook: lets the suite exercise every backend the host can
2822/// execute, not just the fastest one.
2823///
2824/// # Safety
2825///
2826/// This bypasses detection, so **the caller** must establish what detection
2827/// otherwise would: that this CPU can execute `backend`. `backend.is_available()`
2828/// is the portable way to do it. See [`fill_memory_blocks_traced`] for the full
2829/// contract.
2830///
2831/// Guarded, and therefore fine:
2832///
2833/// ```
2834/// # use argon2_rust::{Algorithm, Backend, Params, Version, params::Memory};
2835/// # use argon2_rust::__internal::hash_with_backend;
2836/// # let params = Params::builder().memory(Memory::kib(8)).passes(1).build().unwrap();
2837/// # let mut out = [0u8; 32];
2838/// for &backend in Backend::ALL {
2839///     if !backend.is_available() {
2840///         continue; // this CPU would SIGILL
2841///     }
2842///     // SAFETY: `is_available()` just said this CPU can execute `backend`.
2843///     unsafe {
2844///         hash_with_backend(
2845///             backend, Algorithm::Argon2id, Version::V0x13, &params,
2846///             b"password", b"somesaltsomesalt", &[], &[], &mut out,
2847///         ).unwrap();
2848///     }
2849/// }
2850/// ```
2851///
2852/// The **same snippet with the `unsafe` block deleted** must not compile, which
2853/// is the whole point: safe code cannot reach a `#[target_feature]` function
2854/// whose feature was never detected. Keep these two in sync — the pair is the
2855/// regression test, and the runnable one above is what proves the failing one
2856/// below fails for the right reason rather than through some unrelated typo:
2857///
2858/// ```compile_fail
2859/// # use argon2_rust::{Algorithm, Backend, Params, Version, params::Memory};
2860/// # use argon2_rust::__internal::hash_with_backend;
2861/// # let params = Params::builder().memory(Memory::kib(8)).passes(1).build().unwrap();
2862/// # let mut out = [0u8; 32];
2863/// for &backend in Backend::ALL {
2864///     if !backend.is_available() {
2865///         continue; // this CPU would SIGILL
2866///     }
2867///     hash_with_backend(
2868///         backend, Algorithm::Argon2id, Version::V0x13, &params,
2869///         b"password", b"somesaltsomesalt", &[], &[], &mut out,
2870///     ).unwrap();
2871/// }
2872/// ```
2873///
2874/// # Errors
2875///
2876/// As [`Argon2::hash_into`].
2877///
2878/// # Panics
2879///
2880/// Never.
2881#[cfg(feature = "internal-api")]
2882#[allow(clippy::too_many_arguments)]
2883pub unsafe fn hash_with_backend(
2884    backend: Backend,
2885    algorithm: Algorithm,
2886    version: Version,
2887    params: &Params,
2888    pwd: &[u8],
2889    salt: &[u8],
2890    secret: &[u8],
2891    ad: &[u8],
2892    out: &mut [u8],
2893) -> Result<(), Error> {
2894    // SAFETY: forwarded verbatim from this function's own contract.
2895    unsafe {
2896        hash_inner(
2897            backend, algorithm, version, params, pwd, salt, secret, ad, out,
2898        )
2899    }
2900}
2901
2902#[cfg(test)]
2903mod tests {
2904    use super::*;
2905
2906    // ------------------------------------------------------------------
2907    // decode_bounded — the worker-thread clamp
2908    // ------------------------------------------------------------------
2909
2910    /// The ceiling's `threads` is an OS-thread budget that none of the four
2911    /// magnitude checks implies.
2912    ///
2913    /// Decoding sets `threads = lanes`, and `fill_pooled` spawns
2914    /// `min(threads, lanes) - 1` helpers, so a string whose `p` is *within* the
2915    /// `lanes` ceiling used to hand an attacker that many OS threads on an
2916    /// authentication path. Measured before the clamp: a `p=256` string against
2917    /// a ceiling of `threads = 1` really did spawn 255 helpers.
2918    ///
2919    /// Asserted here rather than by sampling the live thread count, because the
2920    /// hash is over in milliseconds and a sampler misses the peak — which is
2921    /// exactly how this was nearly written off as unreproducible.
2922    #[test]
2923    fn decode_bounded_clamps_workers_to_the_ceilings_thread_budget() {
2924        const LANES: u32 = 256;
2925        let params = Params::builder()
2926            .memory(Memory::kib(u64::from(8 * LANES)))
2927            .passes(1)
2928            .lanes(LANES)
2929            .tag_len(TagLen::bytes(32))
2930            .build()
2931            .expect("params");
2932        let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
2933        let encoded = argon2.hash_encoded(b"pw", b"somesalt").expect("encode");
2934
2935        // "Strings this wide are allowed; spawning this wide is not."
2936        let ceiling = Params::builder()
2937            .memory(Memory::kib(u64::from(8 * LANES)))
2938            .passes(1)
2939            .lanes(LANES)
2940            .threads(1)
2941            .tag_len(TagLen::bytes(32))
2942            .build()
2943            .expect("ceiling");
2944        let decoded =
2945            decode_bounded(&encoded, Algorithm::Argon2id, &ceiling).expect("within the ceiling");
2946
2947        assert_eq!(decoded.params.lanes(), LANES, "lanes must survive: it picks the tag");
2948        assert_eq!(decoded.params.threads(), 1, "workers must obey the ceiling");
2949        assert_eq!(decoded.params.effective_threads(), 1);
2950    }
2951
2952    /// The clamp only ever lowers. A ceiling that permits more workers than the
2953    /// string needs must leave the decoded value alone, so the ordinary ceiling
2954    /// with `.threads()` left unset (where `threads == lanes`) keeps full
2955    /// parallelism.
2956    #[test]
2957    fn decode_bounded_leaves_workers_alone_when_the_ceiling_is_generous() {
2958        let params = Params::builder()
2959            .memory(Memory::kib(1 << 10))
2960            .passes(1)
2961            .lanes(4)
2962            .tag_len(TagLen::bytes(32))
2963            .build()
2964            .expect("params");
2965        let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
2966        let encoded = argon2.hash_encoded(b"pw", b"somesalt").expect("encode");
2967
2968        // `.threads()` unset means threads = lanes = 8, more than the string's 4.
2969        let ceiling = Params::builder()
2970            .memory(Memory::kib(1 << 16))
2971            .passes(8)
2972            .lanes(8)
2973            .tag_len(TagLen::bytes(32))
2974            .build()
2975            .expect("ceiling");
2976        let decoded =
2977            decode_bounded(&encoded, Algorithm::Argon2id, &ceiling).expect("within the ceiling");
2978
2979        assert_eq!(decoded.params.lanes(), 4);
2980        assert_eq!(decoded.params.threads(), 4, "clamped to lanes, not raised to 8");
2981    }
2982
2983    // ------------------------------------------------------------------
2984    // index_alpha
2985    // ------------------------------------------------------------------
2986
2987    fn instance_for(params: &Params, algorithm: Algorithm, arena: &mut [Block]) -> Instance {
2988        // SAFETY: `arena` outlives the returned `Instance` at every call site
2989        // below, and none of these tests index into it.
2990        unsafe {
2991            Instance::new(
2992                arena.as_mut_ptr(),
2993                arena.len(),
2994                algorithm,
2995                Version::V0x13,
2996                params,
2997            )
2998        }
2999    }
3000
3001    #[test]
3002    fn index_alpha_pass0_slice0_is_all_but_the_previous() {
3003        let params = Params::builder()
3004            .memory(Memory::kib(1 << 12))
3005            .passes(1)
3006            .lanes(1)
3007            .tag_len(TagLen::bytes(32))
3008            .build()
3009            .expect("params");
3010        let mut arena = [Block::ZERO; 2];
3011        let inst = instance_for(&params, Algorithm::Argon2i, &mut arena);
3012
3013        // reference_area_size = index - 1, start_position = 0, so the result is
3014        // always < index: index_alpha never returns the block being written.
3015        for index in 2..64u32 {
3016            for pseudo in [0u32, 1, 0x7FFF_FFFF, 0x8000_0000, u32::MAX] {
3017                let pos = Position::new(0, 0, 0, index);
3018                let alpha = index_alpha(&inst, &pos, pseudo, true);
3019                assert!(alpha < index, "index={index} pseudo={pseudo} -> {alpha}");
3020            }
3021        }
3022    }
3023
3024    #[test]
3025    fn index_alpha_never_selects_the_current_or_a_concurrent_block() {
3026        // This is the property the parallel safety argument rests on.
3027        let params = Params::builder()
3028            .memory(Memory::kib(1024))
3029            .passes(3)
3030            .lanes(4)
3031            .threads(4)
3032            .tag_len(TagLen::bytes(32))
3033            .build()
3034            .expect("params");
3035        let mut arena = [Block::ZERO; 2];
3036        let inst = instance_for(&params, Algorithm::Argon2d, &mut arena);
3037        let seg = inst.segment_length;
3038
3039        for pass in 0..3u32 {
3040            for slice in 0..SYNC_POINTS {
3041                for index in 0..seg {
3042                    if pass == 0 && slice == 0 && index < 2 {
3043                        continue;
3044                    }
3045                    let pos = Position::new(pass, 1, slice, index);
3046                    for pseudo in [0u32, 1, 12345, 0x8000_0000, u32::MAX] {
3047                        // Cross-lane: must land outside the current slice.
3048                        // `fill_segment` pins `ref_lane = position.lane` on
3049                        // pass 0 / slice 0, so `same_lane == false` is not
3050                        // reachable there and the C's answer (block 0, the only
3051                        // candidate) is a same-lane reference anyway.
3052                        if !(pass == 0 && slice == 0) {
3053                            let alpha = index_alpha(&inst, &pos, pseudo, false);
3054                            let alpha_slice = alpha / seg;
3055                            assert_ne!(
3056                                alpha_slice, slice,
3057                                "cross-lane reference into the live slice: \
3058                                 pass={pass} slice={slice} index={index} pseudo={pseudo}"
3059                            );
3060                        }
3061
3062                        // Same lane: may be in this slice, but strictly before
3063                        // the block being written.
3064                        let alpha = index_alpha(&inst, &pos, pseudo, true);
3065                        if alpha / seg == slice {
3066                            assert!(
3067                                alpha % seg < index,
3068                                "same-lane reference at or past the current block: \
3069                                 pass={pass} slice={slice} index={index} -> {alpha}"
3070                            );
3071                        }
3072                    }
3073                }
3074            }
3075        }
3076    }
3077
3078    #[test]
3079    fn index_alpha_wraps_at_index_zero_across_lanes() {
3080        // The `((index == 0) ? (-1) : 0)` branch. With slice = 1 and
3081        // segment_length = 2 the C computes reference_area_size = 2 - 1 = 1,
3082        // so the only legal answer is block 0.
3083        let params = Params::builder()
3084            .memory(Memory::kib(8))
3085            .passes(1)
3086            .lanes(1)
3087            .threads(1)
3088            .tag_len(TagLen::bytes(32))
3089            .build()
3090            .expect("params");
3091        let mut arena = [Block::ZERO; 2];
3092        let inst = instance_for(&params, Algorithm::Argon2i, &mut arena);
3093        assert_eq!(inst.segment_length, 2);
3094
3095        let pos = Position::new(0, 0, 1, 0);
3096        for pseudo in [0u32, 1, 0x1234_5678, u32::MAX] {
3097            assert_eq!(index_alpha(&inst, &pos, pseudo, false), 0);
3098        }
3099    }
3100
3101    #[test]
3102    fn index_alpha_start_position_skips_the_current_slice() {
3103        // pass > 0: start_position = (slice + 1) * segment_length, except for
3104        // the last slice where it is 0.
3105        let params = Params::builder()
3106            .memory(Memory::kib(1024))
3107            .passes(2)
3108            .lanes(4)
3109            .threads(4)
3110            .tag_len(TagLen::bytes(32))
3111            .build()
3112            .expect("params");
3113        let mut arena = [Block::ZERO; 2];
3114        let inst = instance_for(&params, Algorithm::Argon2d, &mut arena);
3115        let seg = inst.segment_length;
3116
3117        // pseudo_rand = 0 makes relative_position = ras - 1, the far end of the
3118        // window, so the answer is (start_position + ras - 1) % lane_length.
3119        for slice in 0..SYNC_POINTS {
3120            let pos = Position::new(1, 0, slice, 5);
3121            let ras = inst.lane_length - seg + 5 - 1;
3122            let start = if slice == SYNC_POINTS - 1 {
3123                0
3124            } else {
3125                (slice + 1) * seg
3126            };
3127            assert_eq!(
3128                index_alpha(&inst, &pos, 0, true),
3129                (start + ras - 1) % inst.lane_length
3130            );
3131        }
3132    }
3133
3134    /// `reference_area_size - 1` is evaluated in **`uint32_t`**, not `uint64_t`.
3135    ///
3136    /// This is the one place the task brief's summary and `core.c` disagree, and
3137    /// it is invisible to every other test in this repository — mutating
3138    /// `u64::from(ras.wrapping_sub(1))` into `u64::from(ras).wrapping_sub(1)`
3139    /// leaves the whole suite green, including all 26 official vectors, the
3140    /// KATs and a 95 040-case differential against the C. So it is pinned here
3141    /// directly, against values dumped from the real `index_alpha`.
3142    ///
3143    /// The two readings differ only when `reference_area_size == 0`, where the
3144    /// C gives `relative_position = 0x0000_0000_FFFF_FFFF` and the 64-bit-first
3145    /// reading gives `0xFFFF_FFFF_FFFF_FFFF`. Both then go through
3146    /// `% lane_length`, which hides the difference whenever `lane_length`
3147    /// divides `2^64 - 2^32 = 2^32 * (2^32 - 1)`. Since
3148    /// `2^32 - 1 = 3 * 5 * 17 * 257 * 65537`, that is true for every power of
3149    /// two and for `lane_length` 12 and 20 — which is why a grid of "nice"
3150    /// segment lengths cannot see it. `segment_length` 7, 11, 13, 100 and 341
3151    /// can.
3152    ///
3153    /// `reference_area_size == 0` needs `pass = 0`, `slice = 0`, `index = 1`,
3154    /// which `fill_segment` never produces (it starts at `index = 2` there), so
3155    /// this is unreachable through the public API — but it is still what the C
3156    /// computes, and the next person to "simplify" this line needs a test that
3157    /// stops them.
3158    #[test]
3159    fn index_alpha_reference_area_size_zero_uses_32_bit_arithmetic() {
3160        // Dumped from the C, `index_alpha(&inst, &{0,0,0,1}, r, 1)`:
3161        //   seg=2   lane_length=8    -> 7      (does NOT discriminate)
3162        //   seg=3   lane_length=12   -> 3      (does NOT discriminate)
3163        //   seg=5   lane_length=20   -> 15     (does NOT discriminate)
3164        //   seg=7   lane_length=28   -> 3      (64-bit-first would give 15)
3165        //   seg=11  lane_length=44   -> 3      (64-bit-first would give 15)
3166        //   seg=13  lane_length=52   -> 47     (64-bit-first would give 15)
3167        //   seg=100 lane_length=400  -> 95     (64-bit-first would give 15)
3168        //   seg=341 lane_length=1364 -> 3      (64-bit-first would give 15)
3169        const CASES: [(u32, u32); 8] = [
3170            (2, 7),
3171            (3, 3),
3172            (5, 15),
3173            (7, 3),
3174            (11, 3),
3175            (13, 47),
3176            (100, 95),
3177            (341, 3),
3178        ];
3179
3180        let params = Params::builder()
3181            .memory(Memory::kib(1 << 12))
3182            .passes(1)
3183            .lanes(1)
3184            .tag_len(TagLen::bytes(32))
3185            .build()
3186            .expect("params");
3187        let mut arena = [Block::ZERO; 2];
3188        let mut inst = instance_for(&params, Algorithm::Argon2i, &mut arena);
3189
3190        for (segment_length, expected) in CASES {
3191            inst.segment_length = segment_length;
3192            inst.lane_length = segment_length * SYNC_POINTS;
3193            // pass 0, slice 0, index 1  =>  reference_area_size = 1 - 1 = 0.
3194            let pos = Position::new(0, 0, 0, 1);
3195            for pseudo in [0u32, 1, 0x7FFF_FFFF, 0x8000_0000, u32::MAX, 0xDEAD_BEEF] {
3196                // `reference_area_size == 0` makes `(ras * rel) >> 32` zero for
3197                // every `pseudo_rand`, so the answer does not depend on it.
3198                assert_eq!(
3199                    index_alpha(&inst, &pos, pseudo, true),
3200                    expected,
3201                    "segment_length={segment_length} pseudo={pseudo:#010x}"
3202                );
3203                assert_eq!(index_alpha(&inst, &pos, pseudo, false), expected);
3204            }
3205        }
3206    }
3207
3208    #[test]
3209    fn index_alpha_degenerate_instance_does_not_panic() {
3210        // lane_length == 0 would divide by zero in the C.
3211        let params = Params::builder()
3212            .memory(Memory::kib(8))
3213            .passes(1)
3214            .lanes(1)
3215            .tag_len(TagLen::bytes(32))
3216            .build()
3217            .expect("params");
3218        let mut arena = [Block::ZERO; 2];
3219        let mut inst = instance_for(&params, Algorithm::Argon2i, &mut arena);
3220        inst.lane_length = 0;
3221        inst.segment_length = 0;
3222        assert_eq!(index_alpha(&inst, &Position::new(0, 0, 0, 0), 7, true), 0);
3223    }
3224
3225    // ------------------------------------------------------------------
3226    // constant_time_eq
3227    // ------------------------------------------------------------------
3228
3229    #[test]
3230    fn constant_time_eq_matches_argon2_compare() {
3231        assert!(constant_time_eq(b"", b""));
3232        assert!(constant_time_eq(b"abc", b"abc"));
3233        assert!(!constant_time_eq(b"abc", b"abd"));
3234        assert!(!constant_time_eq(b"abc", b"abcd"));
3235        assert!(!constant_time_eq(b"", b"a"));
3236        // A single differing bit in the last byte.
3237        assert!(!constant_time_eq(&[0u8; 32], &{
3238            let mut b = [0u8; 32];
3239            b[31] = 1;
3240            b
3241        }));
3242        // 0x80 in the high bit: the C's `d - 1` must not sign-extend wrongly.
3243        assert!(!constant_time_eq(&[0u8; 4], &[0, 0, 0, 0x80]));
3244    }
3245
3246    /// Structural guard for the two stable call sites: neither may request the
3247    /// optional H0 output copy from `hash_in_arena`. This observes that API
3248    /// choice, not stack contents; the traced call below proves the counter is
3249    /// live and reserves the copy for the unstable KAT API that returns H0.
3250    #[cfg(feature = "std")]
3251    #[test]
3252    fn stable_hashes_do_not_request_an_h0_output_copy() {
3253        H0_COPY_COUNT.with(|count| count.set(0));
3254
3255        let params = Params::builder()
3256            .memory(Memory::kib(32))
3257            .passes(1)
3258            .lanes(1)
3259            .tag_len(TagLen::bytes(32))
3260            .build()
3261            .expect("params");
3262        let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
3263        let mut tag = [0u8; 32];
3264        argon2
3265            .hash_into(b"password", b"somesalt", &mut tag)
3266            .expect("one-shot hash");
3267        let mut hasher = argon2.hasher();
3268        hasher
3269            .hash_into(b"password", b"somesalt", &mut tag)
3270            .expect("pooled hash");
3271        H0_COPY_COUNT.with(|count| assert_eq!(count.get(), 0, "stable paths copied H0"));
3272
3273        // The hook itself must be live or the zero above would prove nothing.
3274        // SAFETY: the scalar backend is available on every CPU.
3275        let mut h0 = unsafe {
3276            hash_traced(
3277                Backend::Scalar,
3278                argon2.algorithm,
3279                argon2.version,
3280                &argon2.params,
3281                b"password",
3282                b"somesalt",
3283                &[],
3284                &[],
3285                &mut tag,
3286                None,
3287            )
3288        }
3289        .expect("traced hash");
3290        H0_COPY_COUNT.with(|count| assert_eq!(count.get(), 1, "trace did not copy H0"));
3291        clear_internal_memory(&mut h0);
3292    }
3293
3294    #[test]
3295    fn fill_first_blocks_rejects_a_short_internal_arena() {
3296        let mut blockhash = [0xA5; PREHASH_SEED_LENGTH];
3297        let mut arena = [];
3298        assert_eq!(
3299            fill_first_blocks(&mut blockhash, &mut arena, 1, 8),
3300            Err(Error::IncorrectParameter)
3301        );
3302    }
3303
3304    // ------------------------------------------------------------------
3305    // initial_hash
3306    // ------------------------------------------------------------------
3307
3308    #[test]
3309    fn initial_hash_matches_the_genkat_pre_hashing_digest() {
3310        // `phc-winner-argon2/kats/argon2id`, first "Pre-hashing digest" line:
3311        //   t_cost 3, m_cost 32, lanes 4, outlen 32,
3312        //   pwd 32 x 0x01, salt 16 x 0x02, secret 8 x 0x03, ad 12 x 0x04.
3313        let params = Params::builder()
3314            .memory(Memory::kib(32))
3315            .passes(3)
3316            .lanes(4)
3317            .threads(4)
3318            .tag_len(TagLen::bytes(32))
3319            .build()
3320            .expect("params");
3321        let h = initial_hash(
3322            Algorithm::Argon2id,
3323            Version::V0x13,
3324            &params,
3325            &[1u8; 32],
3326            &[2u8; 16],
3327            &[3u8; 8],
3328            &[4u8; 12],
3329        )
3330        .expect("initial_hash");
3331
3332        let expected = "2889de487eb42ae500c0007ed9252f1069eadec40d5765b485de6dc2437a67b8\
3333                        546a2f0acc1a0882db8fcf74714b472e94df421a5da1112ffa11434370a1e997";
3334        let mut hex = String::new();
3335        for byte in &h[..PREHASH_DIGEST_LENGTH] {
3336            hex.push_str(&alloc::format!("{byte:02x}"));
3337        }
3338        assert_eq!(hex, expected);
3339        // The 8 trailing bytes must be zero before `fill_first_blocks` fills them.
3340        assert_eq!(&h[PREHASH_DIGEST_LENGTH..], &[0u8; 8]);
3341    }
3342
3343    #[test]
3344    fn initial_hash_field_order_is_load_bearing() {
3345        // Swapping any two parameters must change H0. Compare `lanes` against
3346        // `outlen`: both are 4, so a transposition would be invisible unless the
3347        // values differ.
3348        let a = Params::builder()
3349            .memory(Memory::kib(64))
3350            .passes(1)
3351            .lanes(2)
3352            .threads(2)
3353            .tag_len(TagLen::bytes(32))
3354            .build()
3355            .expect("params");
3356        let b = Params::builder()
3357            .memory(Memory::kib(64))
3358            .passes(1)
3359            .lanes(4)
3360            .threads(4)
3361            .tag_len(TagLen::bytes(32))
3362            .build()
3363            .expect("params");
3364        let ha = initial_hash(
3365            Algorithm::Argon2i,
3366            Version::V0x13,
3367            &a,
3368            b"p",
3369            b"salt",
3370            &[],
3371            &[],
3372        )
3373        .expect("h");
3374        let hb = initial_hash(
3375            Algorithm::Argon2i,
3376            Version::V0x13,
3377            &b,
3378            b"p",
3379            b"salt",
3380            &[],
3381            &[],
3382        )
3383        .expect("h");
3384        assert_ne!(ha, hb);
3385
3386        // Version and type are hashed separately.
3387        let h10 = initial_hash(
3388            Algorithm::Argon2i,
3389            Version::V0x10,
3390            &a,
3391            b"p",
3392            b"salt",
3393            &[],
3394            &[],
3395        )
3396        .expect("h");
3397        assert_ne!(ha, h10);
3398        let hid = initial_hash(
3399            Algorithm::Argon2id,
3400            Version::V0x13,
3401            &a,
3402            b"p",
3403            b"salt",
3404            &[],
3405            &[],
3406        )
3407        .expect("h");
3408        assert_ne!(ha, hid);
3409
3410        // The length prefixes make "ab" || "" different from "a" || "b".
3411        let h1 = initial_hash(
3412            Algorithm::Argon2i,
3413            Version::V0x13,
3414            &a,
3415            b"ab",
3416            b"saltsalt",
3417            &[],
3418            &[],
3419        )
3420        .expect("h");
3421        let h2 = initial_hash(
3422            Algorithm::Argon2i,
3423            Version::V0x13,
3424            &a,
3425            b"a",
3426            b"bsaltsalt",
3427            &[],
3428            &[],
3429        )
3430        .expect("h");
3431        assert_ne!(h1, h2);
3432    }
3433
3434    // ------------------------------------------------------------------
3435    // The whole pipeline
3436    // ------------------------------------------------------------------
3437
3438    fn hex(bytes: &[u8]) -> String {
3439        let mut s = String::new();
3440        for byte in bytes {
3441            s.push_str(&alloc::format!("{byte:02x}"));
3442        }
3443        s
3444    }
3445
3446    #[test]
3447    fn one_official_vector_end_to_end() {
3448        // test.c: Argon2i v=19 t=2 m=1<<16 p=1 "password" / "somesalt".
3449        let params = Params::builder()
3450            .memory(Memory::kib(1 << 16))
3451            .passes(2)
3452            .lanes(1)
3453            .tag_len(TagLen::bytes(32))
3454            .build()
3455            .expect("params");
3456        let argon2 = Argon2::new(Algorithm::Argon2i, Version::V0x13, params);
3457        let tag = argon2.hash(b"password", b"somesalt").expect("hash");
3458        assert_eq!(
3459            hex(&tag),
3460            "c1628832147d9720c5bd1cfd61367078729f6dfb6f8fea9ff98158e0d7816ed0"
3461        );
3462    }
3463
3464    #[test]
3465    fn genkat_tag_matches_for_all_three_types() {
3466        // The final "Tag:" line of each `phc-winner-argon2/kats/*` file:
3467        // t_cost 3, m_cost 32, lanes 4, outlen 32, pwd 32 x 0x01,
3468        // salt 16 x 0x02, secret 8 x 0x03, ad 12 x 0x04.
3469        let params = Params::builder()
3470            .memory(Memory::kib(32))
3471            .passes(3)
3472            .lanes(4)
3473            .threads(4)
3474            .tag_len(TagLen::bytes(32))
3475            .build()
3476            .expect("params");
3477        for (algorithm, version, expected) in [
3478            (
3479                Algorithm::Argon2d,
3480                Version::V0x13,
3481                "512b391b6f1162975371d30919734294f868e3be3984f3c1a13a4db9fabe4acb",
3482            ),
3483            (
3484                Algorithm::Argon2i,
3485                Version::V0x13,
3486                "c814d9d1dc7f37aa13f0d77f2494bda1c8de6b016dd388d29952a4c4672b6ce8",
3487            ),
3488            (
3489                Algorithm::Argon2id,
3490                Version::V0x13,
3491                "0d640df58d78766c08c037a34a8b53c9d01ef0452d75b65eb52520e96b01e659",
3492            ),
3493            (
3494                Algorithm::Argon2d,
3495                Version::V0x10,
3496                "96a9d4e5a1734092c85e29f410a45914a5dd1f5cbf08b2670da68a0285abf32b",
3497            ),
3498            (
3499                Algorithm::Argon2i,
3500                Version::V0x10,
3501                "87aeedd6517ab830cd9765cd8231abb2e647a5dee08f7c05e02fcb763335d0fd",
3502            ),
3503            (
3504                Algorithm::Argon2id,
3505                Version::V0x10,
3506                "b64615f07789b66b645b67ee9ed3b377ae350b6bfcbb0fc95141ea8f322613c0",
3507            ),
3508        ] {
3509            let argon2 = Argon2::new(algorithm, version, params);
3510            let mut tag = [0u8; 32];
3511            argon2
3512                .hash_into_with_ad(&[1u8; 32], &[2u8; 16], &[3u8; 8], &[4u8; 12], &mut tag)
3513                .expect("hash");
3514            assert_eq!(hex(&tag), expected, "{algorithm:?} {version:?}");
3515        }
3516    }
3517
3518    /// The whole single-threaded pipeline on the smallest legal instance.
3519    ///
3520    /// `m_cost = MIN_MEMORY = 8` blocks, one lane, so the arena is 8 KiB and one
3521    /// pass is 4 slices of `segment_length = 2`. Small enough that
3522    /// `cargo +nightly miri test --lib tiny_` can run allocate → `initial_hash`
3523    /// → `fill_first_blocks` → `fill_segment` → `finalize` → wipe → free end to
3524    /// end. Ground truth from the C reference:
3525    ///
3526    /// ```text
3527    /// printf password | ./argon2 somesalt -{i,d,id} -t 1 -m 3 -p 1 -l 32 -r
3528    /// ```
3529    #[test]
3530    fn tiny_single_threaded_hash_matches_the_c_reference() {
3531        let params = Params::builder()
3532            .memory(Memory::kib(8))
3533            .passes(1)
3534            .lanes(1)
3535            .tag_len(TagLen::bytes(32))
3536            .build()
3537            .expect("params");
3538        assert_eq!(params.memory_layout(), (8, 2, 8));
3539        for (algorithm, expected) in [
3540            (
3541                Algorithm::Argon2i,
3542                "cbf2bce47e6d23999626143fabc5db69164743ee000ddd3f8895a6f82cfb9a6e",
3543            ),
3544            (
3545                Algorithm::Argon2d,
3546                "c519e603ac603ec1aeb5b71ec44a6179e3f3975b14c0c97e3914c79e6363e178",
3547            ),
3548            (
3549                Algorithm::Argon2id,
3550                "f137f8e186a403a679ccd0606e5ab5dcdafe43c1640855ac8c6e33e9bd63eeb3",
3551            ),
3552        ] {
3553            let mut tag = [0u8; 32];
3554            Argon2::new(algorithm, Version::V0x13, params)
3555                .hash_into(b"password", b"somesalt", &mut tag)
3556                .expect("hash");
3557            assert_eq!(hex(&tag), expected, "{algorithm:?}");
3558        }
3559    }
3560
3561    /// The same, two lanes and two passes, so the multi-threaded path and the
3562    /// cross-lane `index_alpha` branches are exercised under Miri too.
3563    ///
3564    /// ```text
3565    /// printf password | ./argon2 somesalt -{i,d,id} -t 2 -m 4 -p 2 -l 32 -r
3566    /// ```
3567    #[test]
3568    fn tiny_two_lane_hash_matches_the_c_reference() {
3569        let params = Params::builder()
3570            .memory(Memory::kib(16))
3571            .passes(2)
3572            .lanes(2)
3573            .tag_len(TagLen::bytes(32))
3574            .build()
3575            .expect("params");
3576        assert_eq!(params.memory_layout(), (16, 2, 8));
3577        for (algorithm, expected) in [
3578            (
3579                Algorithm::Argon2i,
3580                "7fbb85db7e9636115f2fd0f29ea4214baaada18b39fffed7875eeb9fa9b308c5",
3581            ),
3582            (
3583                Algorithm::Argon2d,
3584                "59f20a66a4c31bf0438a2f494867c32120409a91380f0687aefee984ba86bda8",
3585            ),
3586            (
3587                Algorithm::Argon2id,
3588                "747d7631b182faf749d7efc31aec31df4ecfe3b57c792f53800ac2c9978b4888",
3589            ),
3590        ] {
3591            let mut tag = [0u8; 32];
3592            Argon2::new(algorithm, Version::V0x13, params)
3593                .hash_into(b"password", b"somesalt", &mut tag)
3594                .expect("hash");
3595            assert_eq!(hex(&tag), expected, "{algorithm:?} (threads = lanes = 2)");
3596        }
3597    }
3598
3599    #[test]
3600    fn out_length_mismatch_is_out_ptr_mismatch() {
3601        let params = Params::builder()
3602            .memory(Memory::kib(1 << 8))
3603            .passes(1)
3604            .lanes(1)
3605            .tag_len(TagLen::bytes(32))
3606            .build()
3607            .expect("params");
3608        let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
3609        let mut out = [0u8; 16];
3610        assert_eq!(
3611            argon2.hash_into(b"password", b"somesalt", &mut out),
3612            Err(Error::OutPtrMismatch)
3613        );
3614    }
3615
3616    #[test]
3617    fn trace_fires_once_per_pass_with_the_whole_arena() {
3618        let params = Params::builder()
3619            .memory(Memory::kib(1 << 8))
3620            .passes(3)
3621            .lanes(1)
3622            .tag_len(TagLen::bytes(32))
3623            .build()
3624            .expect("params");
3625        let mut passes = alloc::vec::Vec::new();
3626        let mut out = [0u8; 32];
3627        let mut trace = |pass: u32, blocks: &[Block]| {
3628            passes.push((pass, blocks.len()));
3629        };
3630        // SAFETY: `backend()` is what runtime detection picked for this CPU.
3631        let h0 = unsafe {
3632            hash_traced(
3633                crate::fill_block::backend(),
3634                Algorithm::Argon2id,
3635                Version::V0x13,
3636                &params,
3637                b"password",
3638                b"somesalt",
3639                &[],
3640                &[],
3641                &mut out,
3642                Some(&mut trace),
3643            )
3644        }
3645        .expect("hash_traced");
3646
3647        assert_eq!(passes, alloc::vec![(0, 256), (1, 256), (2, 256)]);
3648        assert_eq!(h0.len(), PREHASH_DIGEST_LENGTH);
3649    }
3650
3651    /// A panic on the leader must propagate, not deadlock the pool.
3652    ///
3653    /// The worker pool spans the whole fill and its helpers park on a spin
3654    /// barrier between slices. If the leader unwinds out of `thread::scope`
3655    /// without releasing them, `Scope`'s `Drop` blocks for ever joining threads
3656    /// that are waiting for a `generation` bump that is never coming — the
3657    /// crate hangs instead of failing. This test reaches that path through the
3658    /// one leader-side callback that exists, and it is the reason
3659    /// `ReleaseHelpers` is a `Drop` guard rather than a line at the end of the
3660    /// loop.
3661    ///
3662    /// If this regresses, it does not fail — it hangs. That is the point.
3663    #[test]
3664    #[cfg(feature = "parallel")]
3665    // wasip1 is panic=abort: there is no unwinding to test there.
3666    #[cfg_attr(target_arch = "wasm32", ignore = "no unwinding on wasi (panic=abort)")]
3667    fn a_panicking_trace_callback_unwinds_instead_of_deadlocking_the_pool() {
3668        // 4 lanes and 4 threads, so there really are helpers parked on the
3669        // barrier when the callback runs.
3670        let params = Params::builder()
3671            .memory(Memory::kib(64))
3672            .passes(2)
3673            .lanes(4)
3674            .tag_len(TagLen::bytes(32))
3675            .build()
3676            .expect("params");
3677        let mut arena = Arena::new(params.memory_blocks() as usize).expect("arena");
3678        let mut blockhash = initial_hash(
3679            Algorithm::Argon2id,
3680            Version::V0x13,
3681            &params,
3682            b"password",
3683            b"somesaltsomesalt",
3684            &[],
3685            &[],
3686        )
3687        .expect("H0");
3688        let (_, _, lane_length) = params.memory_layout();
3689        fill_first_blocks(
3690            &mut blockhash,
3691            arena.as_mut_slice(),
3692            params.lanes(),
3693            lane_length,
3694        )
3695        .expect("first blocks");
3696
3697        let caught = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
3698            // SAFETY: `arena` was sized from `params` and outlives `instance`.
3699            let instance = unsafe {
3700                Instance::new(
3701                    arena.as_mut_ptr(),
3702                    arena.len(),
3703                    Algorithm::Argon2id,
3704                    Version::V0x13,
3705                    &params,
3706                )
3707            };
3708            let mut boom = |_pass: u32, _blocks: &[Block]| panic!("trace exploded");
3709            // SAFETY: `Backend::Scalar` runs anywhere, and `instance` is valid.
3710            unsafe {
3711                fill_memory_blocks_traced(&instance, Backend::Scalar, Some(&mut boom)).expect("fill")
3712            };
3713        }));
3714
3715        assert!(caught.is_err(), "the callback's panic must reach the caller");
3716    }
3717
3718    #[test]
3719    fn threads_do_not_change_the_tag() {
3720        // Spec item (12): only `lanes` affects the tag.
3721        for lanes in [2u32, 4] {
3722            let single = Params::builder()
3723                .memory(Memory::kib(1 << 10))
3724                .passes(2)
3725                .lanes(lanes)
3726                .threads(1)
3727                .tag_len(TagLen::bytes(32))
3728                .build()
3729                .expect("params");
3730            let multi = Params::builder()
3731                .memory(Memory::kib(1 << 10))
3732                .passes(2)
3733                .lanes(lanes)
3734                .threads(lanes)
3735                .tag_len(TagLen::bytes(32))
3736                .build()
3737                .expect("params");
3738            let a = Argon2::new(Algorithm::Argon2id, Version::V0x13, single)
3739                .hash(b"password", b"somesalt")
3740                .expect("st");
3741            let b = Argon2::new(Algorithm::Argon2id, Version::V0x13, multi)
3742                .hash(b"password", b"somesalt")
3743                .expect("mt");
3744            assert_eq!(a, b, "lanes={lanes}");
3745        }
3746    }
3747
3748    #[test]
3749    fn verify_round_trips_and_rejects() {
3750        let params = Params::builder()
3751            .memory(Memory::kib(1 << 8))
3752            .passes(2)
3753            .lanes(1)
3754            .tag_len(TagLen::bytes(32))
3755            .build()
3756            .expect("params");
3757        let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
3758        let encoded = argon2.hash_encoded(b"password", b"somesalt").expect("enc");
3759        assert!(encoded.starts_with("$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ$"));
3760
3761        assert_eq!(
3762            Argon2::verify_encoded(&encoded, b"password", Algorithm::Argon2id),
3763            Ok(())
3764        );
3765        assert_eq!(
3766            Argon2::verify_encoded(&encoded, b"passwore", Algorithm::Argon2id),
3767            Err(Error::VerifyMismatch)
3768        );
3769        assert_eq!(
3770            Argon2::verify_encoded(&encoded, b"password", Algorithm::Argon2i),
3771            Err(Error::DecodingFail)
3772        );
3773
3774        let tag = argon2.hash(b"password", b"somesalt").expect("hash");
3775        assert_eq!(argon2.verify(b"password", b"somesalt", &tag), Ok(()));
3776        assert_eq!(
3777            argon2.verify(b"password", b"somesalt", &tag[..16]),
3778            Err(Error::VerifyMismatch)
3779        );
3780    }
3781
3782    #[test]
3783    fn password_flavoured_names_are_the_same_functions() {
3784        let params = Params::builder()
3785            .memory(Memory::kib(1 << 8))
3786            .passes(2)
3787            .lanes(1)
3788            .tag_len(TagLen::bytes(32))
3789            .build()
3790            .expect("params");
3791        let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
3792
3793        let mut a = [0u8; 32];
3794        let mut b = [0u8; 32];
3795        argon2
3796            .hash_into(b"password", b"somesalt", &mut a)
3797            .expect("hash_into");
3798        argon2
3799            .hash_password_into(b"password", b"somesalt", &mut b)
3800            .expect("hash_password_into");
3801        assert_eq!(a, b);
3802
3803        let encoded = argon2.hash_password(b"password", b"somesalt").expect("enc");
3804        assert_eq!(
3805            encoded,
3806            argon2.hash_encoded(b"password", b"somesalt").expect("enc")
3807        );
3808        assert!(encoded.starts_with("$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ$"));
3809
3810        assert_eq!(
3811            Argon2::verify_password(&encoded, b"password", Algorithm::Argon2id),
3812            Ok(())
3813        );
3814        assert_eq!(
3815            Argon2::verify_password(&encoded, b"passwore", Algorithm::Argon2id),
3816            Err(Error::VerifyMismatch)
3817        );
3818    }
3819
3820    // ------------------------------------------------------------------
3821    // Hasher — the pooled arena
3822    // ------------------------------------------------------------------
3823
3824    /// Everything one hash can be observed to produce: the pre-hashing digest,
3825    /// the whole arena after every pass, and the tag.
3826    ///
3827    /// The arena dumps are the point. A tag comparison would prove the two
3828    /// paths agree; a word-by-word arena comparison proves they agree *for the
3829    /// same reason*, and says exactly which block diverged when they do not.
3830    /// This is `genkat.c`'s `internal_kat` output in memory instead of on
3831    /// stdout — the same evidence `tests/kat.rs` checks against the golden
3832    /// files, applied to the one axis those files cannot see: where the arena
3833    /// came from.
3834    type Dump = (
3835        alloc::vec::Vec<(u32, alloc::vec::Vec<Block>)>,
3836        [u8; PREHASH_DIGEST_LENGTH],
3837        [u8; 32],
3838    );
3839
3840    /// One hash down the one-shot path (`Arena::new` .. `Arena::drop`).
3841    ///
3842    /// # Safety
3843    ///
3844    /// This CPU must be able to execute `backend`.
3845    unsafe fn dump_one_shot(backend: Backend, argon2: &Argon2, pwd: &[u8], salt: &[u8]) -> Dump {
3846        let mut tag = [0u8; 32];
3847        let mut passes: alloc::vec::Vec<(u32, alloc::vec::Vec<Block>)> = alloc::vec::Vec::new();
3848        let mut trace = |pass: u32, blocks: &[Block]| passes.push((pass, blocks.to_vec()));
3849        // SAFETY: forwarded verbatim from this function's own contract.
3850        let h0 = unsafe {
3851            hash_traced(
3852                backend,
3853                argon2.algorithm,
3854                argon2.version,
3855                &argon2.params,
3856                pwd,
3857                salt,
3858                &[3u8; 8],
3859                &[4u8; 12],
3860                &mut tag,
3861                Some(&mut trace),
3862            )
3863        }
3864        .expect("one-shot hash");
3865        (passes, h0, tag)
3866    }
3867
3868    /// The same hash over an arena borrowed from `workspace`.
3869    ///
3870    /// # Safety
3871    ///
3872    /// This CPU must be able to execute `backend`.
3873    unsafe fn dump_pooled(
3874        workspace: &mut Workspace,
3875        backend: Backend,
3876        argon2: &Argon2,
3877        pwd: &[u8],
3878        salt: &[u8],
3879    ) -> Dump {
3880        let mut tag = [0u8; 32];
3881        let mut h0 = [0u8; PREHASH_DIGEST_LENGTH];
3882        let mut passes: alloc::vec::Vec<(u32, alloc::vec::Vec<Block>)> = alloc::vec::Vec::new();
3883        let mut trace = |pass: u32, blocks: &[Block]| passes.push((pass, blocks.to_vec()));
3884        // SAFETY: forwarded verbatim from this function's own contract.
3885        unsafe {
3886            hash_in_workspace(
3887                workspace,
3888                backend,
3889                argon2.algorithm,
3890                argon2.version,
3891                &argon2.params,
3892                pwd,
3893                salt,
3894                &[3u8; 8],
3895                &[4u8; 12],
3896                &mut tag,
3897                Some(&mut trace),
3898                Some(&mut h0),
3899            )
3900        }
3901        .expect("pooled hash");
3902        (passes, h0, tag)
3903    }
3904
3905    /// Report the *first* divergence, not a 96 KiB `assert_eq!` diff.
3906    fn assert_same_dump(what: &str, expected: &Dump, actual: &Dump) {
3907        assert_eq!(actual.1, expected.1, "{what}: H0 differs");
3908        assert_eq!(actual.0.len(), expected.0.len(), "{what}: pass count");
3909
3910        for (want, got) in expected.0.iter().zip(actual.0.iter()) {
3911            assert_eq!(got.0, want.0, "{what}: pass index");
3912            assert_eq!(
3913                got.1.len(),
3914                want.1.len(),
3915                "{what}: arena length after pass {}",
3916                want.0
3917            );
3918            for (block, (wb, gb)) in want.1.iter().zip(got.1.iter()).enumerate() {
3919                for (word, (w, g)) in wb.0.iter().zip(gb.0.iter()).enumerate() {
3920                    assert_eq!(
3921                        g, w,
3922                        "{what}: pass {}, block {block}, word {word}",
3923                        want.0
3924                    );
3925                }
3926            }
3927        }
3928        assert_eq!(actual.2, expected.2, "{what}: tag differs");
3929    }
3930
3931    /// The headline correctness claim, checked at the strongest granularity
3932    /// available: a pooled hash must produce a **byte-identical arena** at every
3933    /// pass boundary, not merely an identical tag.
3934    ///
3935    /// Rounds 1 and 2 are the ones that matter — round 0 runs on a
3936    /// freshly-allocated arena, so only a later round can catch a reused arena
3937    /// leaking a previous tenant's bytes into the computation. `genkat.c`'s
3938    /// parameters are used because they are the ones the golden files pin, and
3939    /// `lanes = threads = 4` puts the `std::thread::scope` path under the same
3940    /// check as the single-threaded one.
3941    #[test]
3942    fn a_pooled_hash_reproduces_the_one_shot_arena_word_for_word() {
3943        let params = Params::builder()
3944            .memory(Memory::kib(32))
3945            .passes(3)
3946            .lanes(4)
3947            .threads(4)
3948            .tag_len(TagLen::bytes(32))
3949            .build()
3950            .expect("params");
3951
3952        for algorithm in [Algorithm::Argon2d, Algorithm::Argon2i, Algorithm::Argon2id] {
3953            for version in [Version::V0x10, Version::V0x13] {
3954                let argon2 = Argon2::new(algorithm, version, params);
3955
3956                for &backend in Backend::ALL {
3957                    if !backend.is_available() {
3958                        continue; // this CPU would SIGILL
3959                    }
3960                    // SAFETY: guarded by `is_available()` immediately above.
3961                    let expected =
3962                        unsafe { dump_one_shot(backend, &argon2, &[1u8; 32], &[2u8; 16]) };
3963
3964                    let mut workspace = Workspace::new();
3965                    for round in 0..3 {
3966                        // SAFETY: as above.
3967                        let actual = unsafe {
3968                            dump_pooled(&mut workspace, backend, &argon2, &[1u8; 32], &[2u8; 16])
3969                        };
3970                        assert_same_dump(
3971                            &alloc::format!("{algorithm:?} {version:?} {backend} round {round}"),
3972                            &expected,
3973                            &actual,
3974                        );
3975                    }
3976                }
3977            }
3978        }
3979    }
3980
3981    /// The `Hasher` API itself, against the one-shot API, over enough parameter
3982    /// shapes to cover single-threaded, multi-lane threaded, and multi-pass.
3983    #[test]
3984    fn hasher_agrees_with_the_one_shot_api() {
3985        let configs = [
3986            Params::builder()
3987                .memory(Memory::kib(8))
3988                .passes(1)
3989                .lanes(1)
3990                .tag_len(TagLen::bytes(32))
3991                .build()
3992                .expect("minimum"),
3993            Params::builder()
3994                .memory(Memory::kib(1 << 8))
3995                .passes(2)
3996                .lanes(1)
3997                .tag_len(TagLen::bytes(32))
3998                .build()
3999                .expect("st"),
4000            Params::builder()
4001                .memory(Memory::kib(1 << 9))
4002                .passes(2)
4003                .lanes(4)
4004                .threads(4)
4005                .tag_len(TagLen::bytes(32))
4006                .build()
4007                .expect("mt"),
4008            Params::builder()
4009                .memory(Memory::kib(64))
4010                .passes(3)
4011                .lanes(2)
4012                .threads(2)
4013                .tag_len(TagLen::bytes(24))
4014                .build()
4015                .expect("odd outlen"),
4016        ];
4017
4018        for params in configs {
4019            for algorithm in [Algorithm::Argon2d, Algorithm::Argon2i, Algorithm::Argon2id] {
4020                let argon2 = Argon2::new(algorithm, Version::V0x13, params);
4021                let mut hasher = argon2.hasher();
4022
4023                for round in 0..4u8 {
4024                    let pwd = [round; 7];
4025                    let mut want = alloc::vec![0u8; params.tag_len_bytes()];
4026                    let mut got = alloc::vec![0u8; params.tag_len_bytes()];
4027
4028                    argon2.hash_into(&pwd, b"somesalt", &mut want).expect("one");
4029                    hasher.hash_into(&pwd, b"somesalt", &mut got).expect("pool");
4030                    assert_eq!(got, want, "{algorithm:?} round {round}");
4031
4032                    // ...and with a secret and associated data.
4033                    argon2
4034                        .hash_into_with_ad(&pwd, b"somesalt", &[3u8; 8], &[4u8; 12], &mut want)
4035                        .expect("one ad");
4036                    hasher
4037                        .hash_into_with_ad(&pwd, b"somesalt", &[3u8; 8], &[4u8; 12], &mut got)
4038                        .expect("pool ad");
4039                    assert_eq!(got, want, "{algorithm:?} round {round} with ad");
4040                }
4041            }
4042        }
4043    }
4044
4045    /// The pooled counterparts of `tiny_single_threaded_hash_matches_the_c_reference`
4046    /// and `tiny_two_lane_hash_matches_the_c_reference`, against the same ground
4047    /// truth from the C reference.
4048    ///
4049    /// Sized so that `cargo +nightly miri test --lib tiny_` can run the whole
4050    /// new path end to end: acquire → hash → release-and-wipe → **re**-acquire →
4051    /// hash. The two-lane half puts the `std::thread::scope` raw-pointer sharing
4052    /// over an arena that has already been used once, which is the one piece of
4053    /// unsafe territory reuse actually changes. The growth step at the end makes
4054    /// Miri watch the old allocation being freed while the new one is filled.
4055    #[test]
4056    fn tiny_pooled_hashes_match_the_c_reference() {
4057        // `printf password | ./argon2 somesalt -id -t 1 -m 3 -p 1 -l 32 -r`
4058        let one_lane = Params::builder()
4059            .memory(Memory::kib(8))
4060            .passes(1)
4061            .lanes(1)
4062            .tag_len(TagLen::bytes(32))
4063            .build()
4064            .expect("params");
4065        // `printf password | ./argon2 somesalt -id -t 2 -m 4 -p 2 -l 32 -r`
4066        let two_lane = Params::builder()
4067            .memory(Memory::kib(16))
4068            .passes(2)
4069            .lanes(2)
4070            .tag_len(TagLen::bytes(32))
4071            .build()
4072            .expect("params");
4073
4074        let mut hasher = Argon2::new(Algorithm::Argon2id, Version::V0x13, one_lane).hasher();
4075        let mut tag = [0u8; 32];
4076
4077        for round in 0..2 {
4078            hasher
4079                .hash_into(b"password", b"somesalt", &mut tag)
4080                .expect("single lane");
4081            assert_eq!(
4082                hex(&tag),
4083                "f137f8e186a403a679ccd0606e5ab5dcdafe43c1640855ac8c6e33e9bd63eeb3",
4084                "single lane, round {round}"
4085            );
4086        }
4087
4088        // Same hasher, wider configuration: the arena grows once, then reuses.
4089        hasher.set_argon2(Argon2::new(
4090            Algorithm::Argon2id,
4091            Version::V0x13,
4092            two_lane,
4093        ));
4094        for round in 0..2 {
4095            hasher
4096                .hash_into(b"password", b"somesalt", &mut tag)
4097                .expect("two lanes");
4098            assert_eq!(
4099                hex(&tag),
4100                "747d7631b182faf749d7efc31aec31df4ecfe3b57c792f53800ac2c9978b4888",
4101                "two lanes, round {round}"
4102            );
4103        }
4104
4105        // And back down: the big arena is kept and re-lent as a narrow window.
4106        hasher.set_argon2(Argon2::new(
4107            Algorithm::Argon2id,
4108            Version::V0x13,
4109            one_lane,
4110        ));
4111        hasher
4112            .hash_into(b"password", b"somesalt", &mut tag)
4113            .expect("single lane again");
4114        assert_eq!(
4115            hex(&tag),
4116            "f137f8e186a403a679ccd0606e5ab5dcdafe43c1640855ac8c6e33e9bd63eeb3"
4117        );
4118        assert_eq!(hasher.reserved_blocks(), two_lane.memory_blocks() as usize);
4119    }
4120
4121    /// Reuse is not a claim, it is an address: every hash after the first must
4122    /// land on the same allocation.
4123    #[test]
4124    fn reuse_lands_on_one_allocation() {
4125        let params = Params::builder()
4126            .memory(Memory::kib(1 << 8))
4127            .passes(2)
4128            .lanes(1)
4129            .tag_len(TagLen::bytes(32))
4130            .build()
4131            .expect("params");
4132        let blocks = params.memory_blocks() as usize;
4133        let mut hasher = Argon2::new(Algorithm::Argon2id, Version::V0x13, params).hasher();
4134
4135        assert_eq!(hasher.reserved_blocks(), 0, "nothing allocated up front");
4136
4137        let mut tag = [0u8; 32];
4138        hasher.hash_into(b"password", b"somesalt", &mut tag).expect("first");
4139        assert_eq!(hasher.reserved_blocks(), blocks);
4140
4141        // Peek at the parked arena the way the next hash would. The guard drops
4142        // at the end of the statement, handing it straight back.
4143        let first = hasher.workspace.acquire(blocks).expect("peek").as_ptr();
4144        for round in 0..8 {
4145            hasher.hash_into(b"password", b"somesalt", &mut tag).expect("again");
4146            assert_eq!(
4147                hasher.workspace.acquire(blocks).expect("peek").as_ptr(),
4148                first,
4149                "round {round} reallocated"
4150            );
4151        }
4152        assert_eq!(hasher.reserved_blocks(), blocks);
4153    }
4154
4155    /// The control for the wipe test below, and a fact worth pinning in its own
4156    /// right: a finished hash leaves the **whole** arena full of material
4157    /// derived from that password. There is something real to wipe.
4158    ///
4159    /// Without this, `the_arena_a_hash_borrowed_comes_back_wiped` would be
4160    /// worthless — an arena that was never written would also read as all-zero.
4161    #[test]
4162    fn a_finished_hash_leaves_the_whole_arena_full_of_derived_material() {
4163        let params = Params::builder()
4164            .memory(Memory::kib(1 << 8))
4165            .passes(2)
4166            .lanes(1)
4167            .tag_len(TagLen::bytes(32))
4168            .build()
4169            .expect("params");
4170        let mut arena = Arena::new(params.memory_blocks() as usize).expect("arena");
4171        let mut out = [0u8; 32];
4172
4173        // SAFETY: `Backend::Scalar` is available on every CPU.
4174        unsafe {
4175            hash_in_arena(
4176                &mut arena,
4177                Backend::Scalar,
4178                Algorithm::Argon2id,
4179                Version::V0x13,
4180                &params,
4181                b"password",
4182                b"somesalt",
4183                &[],
4184                &[],
4185                &mut out,
4186                None,
4187                None,
4188            )
4189        }
4190        .expect("hash");
4191
4192        let dirty = arena.as_slice().iter().filter(|b| **b != Block::ZERO).count();
4193        assert_eq!(
4194            dirty,
4195            arena.len(),
4196            "every block should still hold derived material before the wipe"
4197        );
4198    }
4199
4200    /// The security property reuse must not weaken: the arena is wiped when the
4201    /// call that borrowed it returns, so what is parked between calls is zero,
4202    /// not the last password's derived material.
4203    ///
4204    /// Its control is
4205    /// [`a_finished_hash_leaves_the_whole_arena_full_of_derived_material`],
4206    /// which proves the bytes this test demands be gone were there to begin
4207    /// with.
4208    #[test]
4209    #[cfg(feature = "zeroize-memory")]
4210    fn the_arena_a_hash_borrowed_comes_back_wiped() {
4211        let params = Params::builder()
4212            .memory(Memory::kib(1 << 8))
4213            .passes(2)
4214            .lanes(1)
4215            .tag_len(TagLen::bytes(32))
4216            .build()
4217            .expect("params");
4218        let blocks = params.memory_blocks() as usize;
4219        let mut hasher = Argon2::new(Algorithm::Argon2id, Version::V0x13, params).hasher();
4220
4221        let mut tag = [0u8; 32];
4222        for round in 0..3 {
4223            hasher.hash_into(b"password", b"somesalt", &mut tag).expect("hash");
4224            let parked = hasher.workspace.acquire(blocks).expect("peek");
4225            assert!(
4226                parked.as_slice().iter().all(|b| *b == Block::ZERO),
4227                "round {round}: the arena still holds derived material"
4228            );
4229        }
4230    }
4231
4232    /// A hash that fails validation must leave the hasher exactly as it was —
4233    /// no half-released arena, no lost capacity, no wrong answer afterwards.
4234    #[test]
4235    fn an_error_does_not_disturb_reuse() {
4236        let params = Params::builder()
4237            .memory(Memory::kib(1 << 8))
4238            .passes(2)
4239            .lanes(1)
4240            .tag_len(TagLen::bytes(32))
4241            .build()
4242            .expect("params");
4243        let blocks = params.memory_blocks() as usize;
4244        let mut hasher = Argon2::new(Algorithm::Argon2id, Version::V0x13, params).hasher();
4245
4246        let mut tag = [0u8; 32];
4247        hasher.hash_into(b"password", b"somesalt", &mut tag).expect("warm up");
4248        let before = hasher.workspace.acquire(blocks).expect("peek").as_ptr();
4249
4250        // Wrong output length: rejected before anything is allocated.
4251        let mut short = [0u8; 16];
4252        assert_eq!(
4253            hasher.hash_into(b"password", b"somesalt", &mut short),
4254            Err(Error::OutPtrMismatch)
4255        );
4256        // Salt too short: rejected by `validate_for`.
4257        assert!(hasher.hash_into(b"password", b"salt", &mut tag).is_err());
4258
4259        assert_eq!(hasher.reserved_blocks(), blocks, "capacity survived");
4260        assert_eq!(
4261            hasher.workspace.acquire(blocks).expect("peek").as_ptr(),
4262            before,
4263            "and it is the same allocation"
4264        );
4265
4266        let mut after = [0u8; 32];
4267        hasher.hash_into(b"password", b"somesalt", &mut after).expect("still works");
4268        assert_eq!(after, tag);
4269    }
4270
4271    /// One hasher, several configurations. Growth reallocates once; shrinking
4272    /// keeps the big arena; every answer still matches the one-shot API.
4273    #[test]
4274    fn changing_the_configuration_keeps_the_memory_and_the_answers() {
4275        let small = Params::builder()
4276            .memory(Memory::kib(1 << 8))
4277            .passes(1)
4278            .lanes(1)
4279            .tag_len(TagLen::bytes(32))
4280            .build()
4281            .expect("small");
4282        let large = Params::builder()
4283            .memory(Memory::kib(1 << 10))
4284            .passes(1)
4285            .lanes(1)
4286            .tag_len(TagLen::bytes(32))
4287            .build()
4288            .expect("large");
4289        let mut hasher = Argon2::new(Algorithm::Argon2id, Version::V0x13, small).hasher();
4290
4291        let mut tag = [0u8; 32];
4292        let mut want = [0u8; 32];
4293
4294        for (params, label) in [(small, "small"), (large, "large"), (small, "small again")] {
4295            let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
4296            hasher.set_argon2(argon2);
4297            assert_eq!(hasher.params().memory_kib(), params.memory_kib(), "{label}");
4298            assert_eq!(hasher.algorithm(), Algorithm::Argon2id);
4299            assert_eq!(hasher.version(), Version::V0x13);
4300            assert_eq!(hasher.argon2(), &argon2);
4301
4302            hasher.hash_into(b"password", b"somesalt", &mut tag).expect(label);
4303            argon2.hash_into(b"password", b"somesalt", &mut want).expect(label);
4304            assert_eq!(tag, want, "{label}");
4305        }
4306
4307        assert_eq!(
4308            hasher.reserved_blocks(),
4309            large.memory_blocks() as usize,
4310            "a smaller configuration must not shrink the arena"
4311        );
4312    }
4313
4314    /// `reserve` front-loads the allocation; `clear` gives it back. Neither
4315    /// changes an answer.
4316    #[test]
4317    fn reserve_and_clear_move_the_allocation_around() {
4318        let params = Params::builder()
4319            .memory(Memory::kib(1 << 8))
4320            .passes(1)
4321            .lanes(1)
4322            .tag_len(TagLen::bytes(32))
4323            .build()
4324            .expect("params");
4325        let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
4326        let mut hasher = argon2.hasher();
4327
4328        hasher.reserve().expect("reserve");
4329        assert_eq!(hasher.reserved_blocks(), params.memory_blocks() as usize);
4330        let reserved = hasher
4331            .workspace
4332            .acquire(params.memory_blocks() as usize)
4333            .expect("peek")
4334            .as_ptr();
4335
4336        let mut tag = [0u8; 32];
4337        hasher.hash_into(b"password", b"somesalt", &mut tag).expect("hash");
4338        assert_eq!(
4339            hasher
4340                .workspace
4341                .acquire(params.memory_blocks() as usize)
4342                .expect("peek")
4343                .as_ptr(),
4344            reserved,
4345            "the first hash must use the reserved arena, not a new one"
4346        );
4347
4348        hasher.clear();
4349        assert_eq!(hasher.reserved_blocks(), 0);
4350
4351        let mut again = [0u8; 32];
4352        hasher.hash_into(b"password", b"somesalt", &mut again).expect("after clear");
4353        assert_eq!(again, tag);
4354        assert_eq!(hasher.reserved_blocks(), params.memory_blocks() as usize);
4355    }
4356
4357    /// The encoded and verifying halves of the API, including the one method
4358    /// that takes its parameters from the string rather than from the hasher.
4359    #[test]
4360    fn hasher_encodes_and_verifies_like_argon2() {
4361        let params = Params::builder()
4362            .memory(Memory::kib(1 << 8))
4363            .passes(2)
4364            .lanes(1)
4365            .tag_len(TagLen::bytes(32))
4366            .build()
4367            .expect("params");
4368        let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
4369        let mut hasher = argon2.hasher();
4370
4371        let encoded = hasher.hash_encoded(b"password", b"somesalt").expect("enc");
4372        assert_eq!(
4373            encoded,
4374            argon2.hash_encoded(b"password", b"somesalt").expect("enc")
4375        );
4376        assert_eq!(
4377            encoded,
4378            hasher.hash_password(b"password", b"somesalt").expect("enc")
4379        );
4380
4381        assert_eq!(
4382            hasher.verify_encoded(&encoded, b"password", Algorithm::Argon2id),
4383            Ok(())
4384        );
4385        assert_eq!(
4386            hasher.verify_password(&encoded, b"passwore", Algorithm::Argon2id),
4387            Err(Error::VerifyMismatch)
4388        );
4389        assert_eq!(
4390            hasher.verify_encoded(&encoded, b"password", Algorithm::Argon2i),
4391            Err(Error::DecodingFail)
4392        );
4393
4394        let tag = hasher.hash(b"password", b"somesalt").expect("hash");
4395        assert_eq!(tag, argon2.hash(b"password", b"somesalt").expect("hash"));
4396        assert_eq!(hasher.verify(b"password", b"somesalt", &tag), Ok(()));
4397        assert_eq!(
4398            hasher.verify(b"password", b"somesalt", &tag[..16]),
4399            Err(Error::VerifyMismatch)
4400        );
4401
4402        let mut into = [0u8; 32];
4403        hasher
4404            .hash_password_into(b"password", b"somesalt", &mut into)
4405            .expect("hash_password_into");
4406        assert_eq!(&into[..], &tag[..]);
4407    }
4408
4409    /// `verify_encoded` reads `m_cost` out of the string, so one hasher can be
4410    /// pointed at strings written at different costs. All of them must verify —
4411    /// and none of them may leave the hasher any bigger than its *owner* made
4412    /// it, because the string is untrusted input and a pooled arena is retained.
4413    #[test]
4414    fn verifying_a_mix_of_costs_never_lets_a_string_grow_the_arena() {
4415        let small = Params::builder()
4416            .memory(Memory::kib(1 << 8))
4417            .passes(1)
4418            .lanes(1)
4419            .tag_len(TagLen::bytes(32))
4420            .build()
4421            .expect("small");
4422        let large = Params::builder()
4423            .memory(Memory::kib(1 << 10))
4424            .passes(1)
4425            .lanes(1)
4426            .tag_len(TagLen::bytes(32))
4427            .build()
4428            .expect("large");
4429
4430        let encoded_small = Argon2::new(Algorithm::Argon2id, Version::V0x13, small)
4431            .hash_encoded(b"password", b"somesalt")
4432            .expect("enc small");
4433        let encoded_large = Argon2::new(Algorithm::Argon2id, Version::V0x13, large)
4434            .hash_encoded(b"password", b"somesalt")
4435            .expect("enc large");
4436
4437        // Deliberately configured for neither algorithm nor version: those
4438        // `verify_encoded` does take from the string. The *size* it does not.
4439        let mut hasher = Argon2::new(Algorithm::Argon2i, Version::V0x10, small).hasher();
4440
4441        for round in 0..3 {
4442            assert_eq!(
4443                hasher.verify_encoded(&encoded_large, b"password", Algorithm::Argon2id),
4444                Ok(()),
4445                "round {round} large"
4446            );
4447            assert_eq!(
4448                hasher.verify_encoded(&encoded_small, b"password", Algorithm::Argon2id),
4449                Ok(()),
4450                "round {round} small"
4451            );
4452            assert_eq!(
4453                hasher.reserved_blocks(),
4454                small.memory_blocks() as usize,
4455                "round {round}: the encoded string set the high-water mark"
4456            );
4457        }
4458
4459        // The owner raising the configuration is a different matter: that is a
4460        // deliberate choice, so it pools as normal, and a string of that size
4461        // may then use the arena it paid for.
4462        hasher.set_argon2(Argon2::new(Algorithm::Argon2id, Version::V0x13, large));
4463        assert_eq!(
4464            hasher.verify_encoded(&encoded_large, b"password", Algorithm::Argon2id),
4465            Ok(())
4466        );
4467        assert_eq!(hasher.reserved_blocks(), large.memory_blocks() as usize);
4468    }
4469
4470    /// The rule `verify_encoded` enforces is a *ceiling*, and the ceiling is the
4471    /// owner's configuration — not "whatever is already allocated", which would
4472    /// be zero on a hasher that has not hashed yet and would therefore send
4473    /// every verify down the un-pooled path.
4474    ///
4475    /// So: a decoded cost below the configured one pools even as the very first
4476    /// call, and the arena it leaves behind is never larger than the arena the
4477    /// owner's own next `hash_into` would have taken.
4478    #[test]
4479    fn a_decoded_cost_under_the_configured_one_pools_from_the_very_first_call() {
4480        let tiny = Params::builder()
4481            .memory(Memory::kib(1 << 7))
4482            .passes(1)
4483            .lanes(1)
4484            .tag_len(TagLen::bytes(32))
4485            .build()
4486            .expect("tiny");
4487        let configured = Params::builder()
4488            .memory(Memory::kib(1 << 10))
4489            .passes(1)
4490            .lanes(1)
4491            .tag_len(TagLen::bytes(32))
4492            .build()
4493            .expect("configured");
4494
4495        let encoded_tiny = Argon2::new(Algorithm::Argon2id, Version::V0x13, tiny)
4496            .hash_encoded(b"password", b"somesalt")
4497            .expect("enc tiny");
4498
4499        // Nothing allocated yet, and the first thing this hasher ever does is
4500        // verify somebody else's string.
4501        let mut hasher = Argon2::new(Algorithm::Argon2id, Version::V0x13, configured).hasher();
4502        assert_eq!(hasher.reserved_blocks(), 0);
4503
4504        assert_eq!(
4505            hasher.verify_encoded(&encoded_tiny, b"password", Algorithm::Argon2id),
4506            Ok(())
4507        );
4508        assert_eq!(
4509            hasher.reserved_blocks(),
4510            tiny.memory_blocks() as usize,
4511            "a cost under the ceiling should still use the pool"
4512        );
4513        assert!(
4514            hasher.reserved_blocks() <= configured.memory_blocks() as usize,
4515            "an input must never push the pool past the owner's configuration"
4516        );
4517
4518        // And the owner's own hashing still grows it to the configured size.
4519        let mut tag = [0u8; 32];
4520        hasher
4521            .hash_into(b"password", b"somesalt", &mut tag)
4522            .expect("hash");
4523        assert_eq!(
4524            hasher.reserved_blocks(),
4525            configured.memory_blocks() as usize
4526        );
4527    }
4528
4529    /// A `Hasher` must be movable to whichever worker picks up a request. The
4530    /// matching negative — that it is not `Sync` — is the `compile_fail`
4531    /// doctest on [`Hasher`], which is what stops two threads sharing one arena.
4532    #[test]
4533    fn a_hasher_is_send() {
4534        const fn assert_send<T: Send>() {}
4535        assert_send::<Hasher>();
4536    }
4537
4538    /// `hash_in_arena` is the one place an arena of the wrong size could reach
4539    /// `Instance::new`, whose safety contract is `memory_len == memory_blocks`.
4540    /// It must be an error, never undefined behaviour.
4541    #[test]
4542    fn a_wrongly_sized_arena_is_an_error_not_undefined_behaviour() {
4543        let params = Params::builder()
4544            .memory(Memory::kib(1 << 8))
4545            .passes(1)
4546            .lanes(1)
4547            .tag_len(TagLen::bytes(32))
4548            .build()
4549            .expect("params");
4550        assert_eq!(params.memory_blocks(), 256);
4551        let mut arena = Arena::new(64).expect("64 blocks");
4552        let mut out = [0u8; 32];
4553
4554        // SAFETY: `Backend::Scalar` is available on every CPU.
4555        let result = unsafe {
4556            hash_in_arena(
4557                &mut arena,
4558                Backend::Scalar,
4559                Algorithm::Argon2id,
4560                Version::V0x13,
4561                &params,
4562                b"password",
4563                b"somesalt",
4564                &[],
4565                &[],
4566                &mut out,
4567                None,
4568                None,
4569            )
4570        };
4571        assert_eq!(result.err(), Some(Error::MemoryAllocationError));
4572        assert_eq!(out, [0u8; 32], "nothing was written");
4573    }
4574
4575    #[test]
4576    fn every_available_backend_agrees_with_scalar() {
4577        let params = Params::builder()
4578            .memory(Memory::kib(1 << 9))
4579            .passes(2)
4580            .lanes(2)
4581            .threads(2)
4582            .tag_len(TagLen::bytes(32))
4583            .build()
4584            .expect("params");
4585        let mut reference = [0u8; 32];
4586        // SAFETY: `Backend::Scalar` is available on every CPU.
4587        unsafe {
4588            hash_inner(
4589                Backend::Scalar,
4590                Algorithm::Argon2id,
4591                Version::V0x13,
4592                &params,
4593                b"password",
4594                b"somesalt",
4595                &[],
4596                &[],
4597                &mut reference,
4598            )
4599        }
4600        .expect("scalar");
4601
4602        for &backend in Backend::ALL {
4603            if !backend.is_available() {
4604                continue;
4605            }
4606            let mut out = [0u8; 32];
4607            // SAFETY: guarded by `is_available()` immediately above.
4608            unsafe {
4609                hash_inner(
4610                    backend,
4611                    Algorithm::Argon2id,
4612                    Version::V0x13,
4613                    &params,
4614                    b"password",
4615                    b"somesalt",
4616                    &[],
4617                    &[],
4618                    &mut out,
4619                )
4620            }
4621            .expect("backend");
4622            assert_eq!(out, reference, "{backend}");
4623        }
4624    }
4625}