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