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