Skip to main content

argon2_rust/
core.rs

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