Skip to main content

crypto/argon2/
argon2.rs

1#[cfg(feature = "alloc")]
2extern crate alloc;
3
4#[cfg(feature = "alloc")]
5use alloc::{string::String, vec, vec::Vec};
6use core::mem::MaybeUninit;
7
8use crate::{Hasher, blake2::Blake2b};
9
10#[path = "fill.rs"]
11mod fill;
12
13#[cfg(target_arch = "x86_64")]
14#[path = "fill_avx2.rs"]
15mod fill_avx2;
16
17#[cfg(target_arch = "aarch64")]
18#[path = "fill_neon.rs"]
19mod fill_neon;
20
21#[cfg(test)]
22use fill::{compress, permutation_p};
23
24/// Argon2 version 1.3 (0x13)
25const VERSION: u32 = 0x13;
26
27/// Number of synchronization points (slices per pass)
28const SYNC_POINTS: u32 = 4;
29
30/// Block size in bytes (1024 bytes = 128 u64 values)
31const BLOCK_SIZE: usize = 1024;
32
33/// Argon2 type constants
34#[allow(dead_code)]
35const ARGON2D: u32 = 0;
36const ARGON2I: u32 = 1;
37const ARGON2ID: u32 = 2;
38
39/// Default output length (in bytes) used by [`hash_password`].
40#[cfg(feature = "alloc")]
41const DEFAULT_TAG_LENGTH: usize = 64;
42
43/// Argon2id parameters (RFC 9106).
44///
45/// The output length is not part of the parameters: it is inferred from the
46/// length of the output buffer passed to [`derive_key`].
47///
48/// # Example
49///
50/// ```ignore
51/// use crypto::argon2::Params;
52///
53/// let params = Params {
54///     iterations: 3,
55///     memory: 65536,
56///     parallelism: 4,
57/// };
58/// ```
59#[derive(Debug, Clone)]
60pub struct Params {
61    /// Number of passes (iterations). Must be >= 1.
62    pub iterations: u32,
63    /// Memory size in KiB. Must be >= 8 * `parallelism`.
64    pub memory: u32,
65    /// Degree of parallelism (number of lanes). Must be >= 1.
66    pub parallelism: u32,
67}
68
69impl Default for Params {
70    /// Default parameters: t=3, m=64 MiB, p=4 (SECOND RECOMMENDED option).
71    fn default() -> Self {
72        Params {
73            iterations: 3,
74            memory: 65536,
75            parallelism: 4,
76        }
77    }
78}
79
80#[cfg(feature = "alloc")]
81impl Params {
82    /// Check these parameters against the RFC 9106 bounds and the requested
83    /// output length.
84    ///
85    /// Returns [`Argon2Error::InvalidParams`] if `iterations < 1`,
86    /// `parallelism < 1`, `parallelism > 2^24 - 1`, `memory < 8 * parallelism`,
87    /// `out_len < 4`, or if `out_len` does not fit in a `u32` (Argon2 encodes
88    /// lengths as 32-bit little-endian values).
89    fn validate(&self, out_len: usize) -> Result<(), Argon2Error> {
90        if self.iterations < 1 {
91            return Err(Argon2Error::InvalidParams("iterations must be >= 1"));
92        }
93        if self.parallelism < 1 {
94            return Err(Argon2Error::InvalidParams("parallelism must be >= 1"));
95        }
96        if self.parallelism > (1 << 24) - 1 {
97            return Err(Argon2Error::InvalidParams("parallelism must be <= 2^24 - 1"));
98        }
99        if out_len < 4 {
100            return Err(Argon2Error::InvalidParams("output length must be >= 4"));
101        }
102        check_u32_len(out_len, "output length must be <= 2^32 - 1")?;
103        // Checked after the parallelism bound above so the multiplication
104        // cannot overflow.
105        if self.memory < 8 * self.parallelism {
106            return Err(Argon2Error::InvalidParams("memory must be >= 8*parallelism"));
107        }
108        Ok(())
109    }
110}
111
112/// Return [`Argon2Error::InvalidParams`] if `len` does not fit in a `u32`.
113///
114/// Argon2 encodes every length as a 32-bit little-endian value, so inputs
115/// larger than `u32::MAX` would be silently truncated.
116#[cfg(feature = "alloc")]
117#[inline(always)]
118fn check_u32_len(len: usize, msg: &'static str) -> Result<(), Argon2Error> {
119    if u32::try_from(len).is_err() {
120        Err(Argon2Error::InvalidParams(msg))
121    } else {
122        Ok(())
123    }
124}
125
126/// Argon2 error type.
127#[derive(Debug, Clone, PartialEq, Eq)]
128#[cfg(feature = "alloc")]
129pub enum Argon2Error {
130    /// Invalid parameter
131    InvalidParams(&'static str),
132    /// Invalid encoded string
133    InvalidEncoding(&'static str),
134    /// Password verification failed
135    VerifyMismatch,
136}
137
138#[cfg(feature = "alloc")]
139impl core::fmt::Display for Argon2Error {
140    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
141        match self {
142            Argon2Error::InvalidParams(msg) => write!(f, "argon2: invalid params: {}", msg),
143            Argon2Error::InvalidEncoding(msg) => write!(f, "argon2: invalid encoding: {}", msg),
144            Argon2Error::VerifyMismatch => write!(f, "argon2: verification failed"),
145        }
146    }
147}
148
149/// Derive a key using Argon2id (RFC 9106).
150///
151/// This is the main entry point for Argon2id key derivation. The derived key is
152/// written into `out`; its length determines the Argon2 output length and must
153/// be at least 4 bytes.
154///
155/// # Arguments
156/// * `out` - Output buffer for the derived key. Its length is the key length.
157/// * `password` - The password to hash
158/// * `salt` - Salt (recommended 16 bytes)
159/// * `secret` - Optional secret key (can be empty)
160/// * `ad` - Optional associated data (can be empty)
161/// * `params` - Argon2id parameters
162///
163/// # Errors
164///
165/// Returns [`Argon2Error::InvalidParams`] if `out` is shorter than 4 bytes or
166/// longer than `2^32 - 1` bytes, if `params` is invalid, or if `password`,
167/// `salt`, `secret`, or `ad` is longer than `2^32 - 1` bytes.
168///
169/// # Example
170///
171/// ```ignore
172/// use crypto::argon2::{derive_key, Params};
173///
174/// let mut key = [0u8; 32];
175/// derive_key(
176///     &mut key,
177///     b"correct horse battery staple",
178///     b"randomsalt123456",
179///     &[],  // no secret
180///     &[],  // no associated data
181///     &Params { iterations: 3, memory: 65536, parallelism: 4 },
182/// ).unwrap();
183/// assert_eq!(key.len(), 32);
184/// ```
185#[cfg(feature = "alloc")]
186pub fn derive_key(
187    out: &mut [u8],
188    password: &[u8],
189    salt: &[u8],
190    secret: &[u8],
191    ad: &[u8],
192    params: &Params,
193) -> Result<(), Argon2Error> {
194    argon2_core(ARGON2ID, password, salt, secret, ad, params, out)
195}
196
197/// Hash a password and return the PHC-encoded string.
198///
199/// The output hash is 64 bytes long.
200///
201/// # Example
202///
203/// ```ignore
204/// use crypto::argon2::{hash_password, verify_password, Params};
205///
206/// let encoded = hash_password(
207///     b"correct horse battery staple",
208///     b"randomsalt123456",
209///     &Params { iterations: 3, memory: 65536, parallelism: 4 },
210/// ).unwrap();
211///
212/// assert!(verify_password(b"correct horse battery staple", &encoded, None).is_ok());
213/// assert!(verify_password(b"wrong password", &encoded, None).is_err());
214/// ```
215#[cfg(feature = "alloc")]
216pub fn hash_password(password: &[u8], salt: &[u8], params: &Params) -> Result<String, Argon2Error> {
217    let mut tag = [0u8; DEFAULT_TAG_LENGTH];
218    derive_key(&mut tag, password, salt, &[], &[], params)?;
219    Ok(encode_phc(params, salt, &tag))
220}
221
222/// Verify a password against a PHC-encoded hash string.
223///
224/// `limits` optionally caps the resource usage accepted from the encoded
225/// string. The `m`, `t`, and `p` fields of a PHC hash are attacker-controlled
226/// whenever the hash is not fully trusted (for example a user-supplied or
227/// imported credential), and the decoder would otherwise happily honor a hash
228/// requesting gigabytes of memory or billions of passes. When `limits` is
229/// `Some`, verification fails with [`Argon2Error::InvalidParams`] as soon as
230/// the decoded parameters exceed any of the given bounds, before any memory is
231/// allocated. Pass `None` when the encoded string is trusted.
232///
233/// See [`hash_password`] for an example.
234#[cfg(feature = "alloc")]
235pub fn verify_password(password: &[u8], encoded: &str, limits: Option<&Params>) -> Result<(), Argon2Error> {
236    let (params, salt, expected_tag) = decode_phc(encoded)?;
237
238    if let Some(limits) = limits
239        && (params.iterations > limits.iterations
240            || params.memory > limits.memory
241            || params.parallelism > limits.parallelism)
242    {
243        return Err(Argon2Error::InvalidParams("encoded parameters exceed the configured limits"));
244    }
245
246    let mut computed_tag = vec![0u8; expected_tag.len()];
247    derive_key(&mut computed_tag, password, &salt, &[], &[], &params)?;
248
249    constant_time_eq::constant_time_eq(&computed_tag, &expected_tag).ok_or(Argon2Error::VerifyMismatch)
250}
251
252// ============================================================
253// PHC String Format encode/decode
254// ============================================================
255
256/// Encode an Argon2id hash in the PHC string format:
257/// `$argon2id$v=19$m=<memory>,t=<iterations>,p=<parallelism>$<salt_b64>$<hash_b64>`
258///
259/// Uses base64 encoding without padding (standard alphabet with +/ replaced by the
260/// PHC-standard base64 which is actually the standard base64 without padding).
261#[cfg(feature = "alloc")]
262pub fn encode_phc(params: &Params, salt: &[u8], tag: &[u8]) -> String {
263    let salt_b64 = base64_encode_no_pad(salt);
264    let tag_b64 = base64_encode_no_pad(tag);
265    alloc::format!(
266        "$argon2id$v=19$m={},t={},p={}${}${}",
267        params.memory,
268        params.iterations,
269        params.parallelism,
270        salt_b64,
271        tag_b64
272    )
273}
274
275/// Decode an Argon2id PHC string format into (params, salt, tag).
276///
277/// Expected format: `$argon2id$v=19$m=<m>,t=<t>,p=<p>$<salt_b64>$<hash_b64>`
278///
279/// # Errors
280/// Returns [`Argon2Error::InvalidEncoding`] for a malformed string, and
281/// [`Argon2Error::InvalidParams`] if the decoded parameters violate the
282/// RFC 9106 bounds (`t >= 1`, `p` in `1..=2^24 - 1`, `m >= 8*p`) or if the
283/// decoded tag is shorter than 4 bytes. Note that the salt is not validated.
284#[cfg(feature = "alloc")]
285pub fn decode_phc(encoded: &str) -> Result<(Params, Vec<u8>, Vec<u8>), Argon2Error> {
286    let parts: Vec<&str> = encoded.split('$').collect();
287    // Parts: ["", "argon2id", "v=19", "m=...,t=...,p=...", "<salt>", "<hash>"]
288    if parts.len() != 6 {
289        return Err(Argon2Error::InvalidEncoding("invalid PHC string format"));
290    }
291    if !parts[0].is_empty() {
292        return Err(Argon2Error::InvalidEncoding("must start with $"));
293    }
294    if parts[1] != "argon2id" {
295        return Err(Argon2Error::InvalidEncoding("unsupported algorithm"));
296    }
297    if parts[2] != "v=19" {
298        return Err(Argon2Error::InvalidEncoding("unsupported version"));
299    }
300
301    // Parse params
302    let param_parts: Vec<&str> = parts[3].split(',').collect();
303    if param_parts.len() != 3 {
304        return Err(Argon2Error::InvalidEncoding("invalid parameters"));
305    }
306
307    let memory = parse_param(param_parts[0], "m=")?;
308    let iterations = parse_param(param_parts[1], "t=")?;
309    let parallelism = parse_param(param_parts[2], "p=")?;
310
311    let salt = base64_decode_no_pad(parts[4]).map_err(|_| Argon2Error::InvalidEncoding("invalid base64 in salt"))?;
312    let tag = base64_decode_no_pad(parts[5]).map_err(|_| Argon2Error::InvalidEncoding("invalid base64 in hash"))?;
313
314    let params = Params {
315        iterations,
316        memory,
317        parallelism,
318    };
319
320    // Reject parameters that `derive_key` would reject anyway, so callers can
321    // rely on `decode_phc` returning only usable parameters.
322    params.validate(tag.len())?;
323
324    Ok((params, salt, tag))
325}
326
327// ============================================================
328// Core Argon2 algorithm
329// ============================================================
330
331/// A 1024-byte block used in Argon2's memory matrix.
332///
333/// The 64-byte alignment keeps every block cache-line aligned, which lets the
334/// SIMD backends use aligned loads/stores.
335#[derive(Clone)]
336#[repr(align(64))]
337struct Block {
338    v: [u64; 128],
339}
340
341impl Block {
342    #[inline(always)]
343    const fn zero() -> Self {
344        Block {
345            v: [0u64; 128],
346        }
347    }
348
349    #[inline(always)]
350    fn xor_with(&mut self, other: &Block) {
351        for (dest, source) in self.v.iter_mut().zip(other.v.iter()) {
352            *dest ^= *source;
353        }
354    }
355
356    /// Build a block from its canonical little-endian byte representation.
357    #[inline(always)]
358    fn from_bytes(bytes: &[u8; BLOCK_SIZE]) -> Self {
359        let mut v = [0u64; 128];
360        for (word, chunk) in v.iter_mut().zip(bytes.as_chunks::<8>().0) {
361            *word = u64::from_le_bytes(*chunk);
362        }
363        Block {
364            v,
365        }
366    }
367
368    /// Serialize the block to its canonical little-endian byte representation.
369    #[inline(always)]
370    fn to_bytes(&self) -> [u8; BLOCK_SIZE] {
371        let mut out = [0u8; BLOCK_SIZE];
372        for (chunk, word) in out.as_chunks_mut::<8>().0.iter_mut().zip(self.v.iter()) {
373            *chunk = word.to_le_bytes();
374        }
375        out
376    }
377}
378
379/// Internal function supporting all argon2 types (for testing).
380#[cfg(feature = "alloc")]
381fn argon2_core(
382    argon_type: u32,
383    password: &[u8],
384    salt: &[u8],
385    secret: &[u8],
386    ad: &[u8],
387    params: &Params,
388    out: &mut [u8],
389) -> Result<(), Argon2Error> {
390    argon2_core_with_backend(argon_type, password, salt, secret, ad, params, out, detect_backend())
391}
392
393/// Like [`argon2_core`], but with an explicitly selected compression backend.
394/// Used to run the test vectors against every available implementation.
395#[cfg(feature = "alloc")]
396#[allow(clippy::too_many_arguments)]
397fn argon2_core_with_backend(
398    argon_type: u32,
399    password: &[u8],
400    salt: &[u8],
401    secret: &[u8],
402    ad: &[u8],
403    params: &Params,
404    out: &mut [u8],
405    backend: Backend,
406) -> Result<(), Argon2Error> {
407    // Validate parameters and lengths. Argon2 encodes every length as a 32-bit
408    // little-endian value, so anything larger must be rejected rather than
409    // silently truncated.
410    params.validate(out.len())?;
411    check_u32_len(password.len(), "password must be <= 2^32 - 1 bytes")?;
412    check_u32_len(salt.len(), "salt must be <= 2^32 - 1 bytes")?;
413    check_u32_len(secret.len(), "secret must be <= 2^32 - 1 bytes")?;
414    check_u32_len(ad.len(), "associated data must be <= 2^32 - 1 bytes")?;
415
416    let p = params.parallelism;
417    let t = params.iterations;
418    let m = params.memory;
419    let tag_length = out.len() as u32;
420
421    // Step 1: Compute H_0
422    let h0 = compute_h0(argon_type, password, salt, secret, ad, p, tag_length, m, t);
423
424    // Step 2: Determine actual memory size m' (rounded down to multiple of 4*p)
425    let m_prime = 4 * p * (m / (4 * p));
426    let q = m_prime / p; // columns per lane
427
428    // Allocate memory as m' blocks. This is the only heap allocation performed
429    // by the whole derivation; every other buffer lives on the stack. The arena
430    // is left uninitialized (see `Memory`).
431    let mem = Memory::uninit(m_prime as usize);
432
433    // Step 3 & 4: Compute B[i][0] and B[i][1] for all lanes
434    for i in 0..p {
435        let mut input = [0u8; 72];
436        input[..64].copy_from_slice(&h0);
437        input[68..72].copy_from_slice(&i.to_le_bytes());
438
439        let mut block_bytes = [0u8; BLOCK_SIZE];
440
441        // B[i][0] = H'^(1024)(H_0 || LE32(0) || LE32(i))
442        input[64..68].copy_from_slice(&0u32.to_le_bytes());
443        variable_length_hash_into(&input, &mut block_bytes);
444        mem.write((i * q) as usize, Block::from_bytes(&block_bytes));
445
446        // B[i][1] = H'^(1024)(H_0 || LE32(1) || LE32(i))
447        input[64..68].copy_from_slice(&1u32.to_le_bytes());
448        variable_length_hash_into(&input, &mut block_bytes);
449        mem.write((i * q + 1) as usize, Block::from_bytes(&block_bytes));
450    }
451
452    // Steps 5-6: Fill memory
453    fill_memory(backend, &mem, argon_type, p, q, t, m_prime);
454
455    // Step 7: Compute final block C = XOR of last column
456    let mut final_block = mem.get((q - 1) as usize).clone();
457    for i in 1..p {
458        let idx = (i * q + q - 1) as usize;
459        final_block.xor_with(mem.get(idx));
460    }
461
462    // Step 8: Output tag = H'^T(C)
463    let final_bytes = final_block.to_bytes();
464    variable_length_hash_into(&final_bytes, out);
465
466    Ok(())
467}
468
469/// Fill the whole memory matrix.
470///
471/// When the `std` feature is enabled (and the target is not wasm32) and each
472/// lane has enough work to amortize thread startup, every lane is filled by
473/// its own thread. The threads are scoped to this call: they are spawned here
474/// and joined before it returns, so no worker outlives the derivation. Lanes
475/// synchronize on a barrier at every slice boundary, which is exactly where
476/// Argon2 permits cross-lane reads.
477#[cfg(feature = "alloc")]
478fn fill_memory(backend: Backend, memory: &Memory, argon_type: u32, p: u32, q: u32, t: u32, m_prime: u32) {
479    #[cfg(all(feature = "std", not(target_arch = "wasm32")))]
480    {
481        // Thread startup only pays off for reasonably large segments.
482        const MIN_PARALLEL_SEGMENT_LENGTH: u32 = 64;
483        let segment_length = q / SYNC_POINTS;
484        if p > 1 && segment_length >= MIN_PARALLEL_SEGMENT_LENGTH {
485            // The barrier lives in this frame, so it outlives the scope and its
486            // worker threads. The workers themselves never outlive this call.
487            let barrier = std::sync::Barrier::new(p as usize);
488            std::thread::scope(|scope| {
489                let barrier = &barrier;
490                for lane in 0..p {
491                    scope.spawn(move || {
492                        for pass in 0..t {
493                            for slice in 0..SYNC_POINTS {
494                                fill_segment(backend, memory, argon_type, pass, lane, slice, p, q, t, m_prime);
495                                barrier.wait();
496                            }
497                        }
498                    });
499                }
500            });
501            return;
502        }
503    }
504
505    for pass in 0..t {
506        for slice in 0..SYNC_POINTS {
507            for lane in 0..p {
508                fill_segment(backend, memory, argon_type, pass, lane, slice, p, q, t, m_prime);
509            }
510        }
511    }
512}
513
514/// Selects the SIMD kernel used to fill the memory matrix.
515///
516/// The choice is resolved once per derivation (never per block) and cached for
517/// the whole call.
518#[derive(Clone, Copy, PartialEq, Eq, Debug)]
519#[allow(dead_code)] // `Scalar` is unused when a SIMD backend is always selected.
520enum Backend {
521    /// Portable `u64` implementation. Always available.
522    Scalar,
523    /// AArch64 NEON, which is baseline on `aarch64`.
524    #[cfg(target_arch = "aarch64")]
525    Neon,
526    /// AArch64 NEON with the `sha3` extension, which lets LLVM fuse the
527    /// rotate-xor steps into a single `xar` instruction.
528    #[cfg(target_arch = "aarch64")]
529    NeonSha3,
530    /// x86-64 AVX2.
531    #[cfg(target_arch = "x86_64")]
532    Avx2,
533}
534
535/// Detect the fastest compression kernel available on the running CPU.
536///
537/// Depending on the target architecture and feature set, any one of the
538/// cfg-gated branches below is the terminal path, so they are written as
539/// explicit returns rather than trailing expressions.
540#[allow(clippy::needless_return)]
541fn detect_backend() -> Backend {
542    #[cfg(target_arch = "aarch64")]
543    {
544        #[cfg(feature = "std")]
545        {
546            if std::arch::is_aarch64_feature_detected!("sha3") {
547                return Backend::NeonSha3;
548            }
549            return Backend::Neon;
550        }
551
552        #[cfg(all(not(feature = "std"), target_feature = "sha3"))]
553        return Backend::NeonSha3;
554
555        #[cfg(all(not(feature = "std"), not(target_feature = "sha3")))]
556        return Backend::Neon;
557    }
558
559    #[cfg(target_arch = "x86_64")]
560    {
561        #[cfg(feature = "std")]
562        {
563            if std::arch::is_x86_feature_detected!("avx2") {
564                return Backend::Avx2;
565            }
566            return Backend::Scalar;
567        }
568
569        #[cfg(all(not(feature = "std"), target_feature = "avx2"))]
570        return Backend::Avx2;
571
572        #[cfg(all(not(feature = "std"), not(target_feature = "avx2")))]
573        return Backend::Scalar;
574    }
575
576    #[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))]
577    return Backend::Scalar;
578}
579
580/// Fill a segment of the memory matrix using the selected backend.
581///
582/// The compression kernel is called directly for the selected backend. The
583/// backend is loop-invariant, so the `match` can be hoisted out of the hot
584/// loop. The `#[target_feature]` kernels (`NeonSha3` and `Avx2`) remain
585/// separate calls, since a function compiled with a target feature is never
586/// inlined into a caller that does not enable it.
587#[cfg(feature = "alloc")]
588#[allow(clippy::too_many_arguments)]
589fn fill_segment(
590    backend: Backend,
591    memory: &Memory,
592    argon_type: u32,
593    pass: u32,
594    lane: u32,
595    slice: u32,
596    lanes: u32,
597    q: u32,       // columns per lane
598    t: u32,       // total passes
599    m_prime: u32, // total blocks
600) {
601    let segment_length = q / SYNC_POINTS;
602
603    // For Argon2i and Argon2id (first half of first pass), addresses are
604    // derived from a pseudo-random stream. It is produced 128 words at a time
605    // into a stack buffer, so no heap allocation is needed.
606    let need_pseudo_rands = argon_type == ARGON2I || (argon_type == ARGON2ID && pass == 0 && slice < 2);
607    let mut addr_words = [0u64; 128];
608    let mut addr_chunk = u32::MAX;
609
610    let start_index = if pass == 0 && slice == 0 { 2 } else { 0 };
611
612    for s in start_index..segment_length {
613        let j = slice * segment_length + s; // current column index in this lane
614        let cur_index = (lane * q + j) as usize;
615
616        // Previous block index
617        let prev_index = if j == 0 {
618            (lane * q + q - 1) as usize
619        } else {
620            (lane * q + j - 1) as usize
621        };
622
623        // Determine J1 and J2
624        let (j1, j2) = if need_pseudo_rands {
625            let chunk = s / 128;
626            if chunk != addr_chunk {
627                generate_address_block(pass, lane, slice, t, argon_type, m_prime, (chunk + 1) as u64, &mut addr_words);
628                addr_chunk = chunk;
629            }
630            let val = addr_words[(s % 128) as usize];
631            ((val & 0xFFFFFFFF) as u32, (val >> 32) as u32)
632        } else {
633            // Argon2d mode: use first 64 bits of previous block
634            let word = memory.first_word(prev_index);
635            (word as u32, (word >> 32) as u32)
636        };
637
638        // Map J1, J2 to reference block index
639        let ref_lane = if pass == 0 && slice == 0 { lane } else { j2 % lanes };
640
641        let ref_index = index_alpha(pass, slice, lanes, segment_length, s, q, ref_lane == lane, j1);
642        let ref_block_index = (ref_lane * q + ref_index) as usize;
643
644        // Argon2 never references the block currently being written, so
645        // `cur_index` differs from both `prev_index` and `ref_block_index`.
646        // Cross-lane reads target blocks finalized at this synchronization
647        // point, so they never race with a concurrent write.
648        //
649        // SAFETY: the two shared borrows and the raw pointer target
650        // pairwise-distinct blocks (see `Memory`), and `backend` is only ever
651        // one that `detect_backend` selected for this CPU.
652        unsafe {
653            let prev = memory.get(prev_index);
654            let reference = memory.get(ref_block_index);
655            let cur = memory.get_mut(cur_index);
656            match backend {
657                Backend::Scalar => fill::fill_block(prev, reference, cur, pass != 0),
658                #[cfg(target_arch = "aarch64")]
659                Backend::Neon => fill_neon::fill_block(prev, reference, cur, pass != 0),
660                #[cfg(target_arch = "aarch64")]
661                Backend::NeonSha3 => fill_neon::fill_block_sha3(prev, reference, cur, pass != 0),
662                #[cfg(target_arch = "x86_64")]
663                Backend::Avx2 => fill_avx2::fill_block(prev, reference, cur, pass != 0),
664            }
665        }
666    }
667}
668
669/// Fill `out` with one 128-word block of pseudo-random addresses for
670/// Argon2i/Argon2id data-independent addressing.
671#[cfg(feature = "alloc")]
672fn generate_address_block(
673    pass: u32,
674    lane: u32,
675    slice: u32,
676    t: u32,
677    argon_type: u32,
678    m_prime: u32,
679    counter: u64,
680    out: &mut [u64; 128],
681) {
682    // Build input block
683    let mut input = Block::zero();
684    input.v[0] = pass as u64;
685    input.v[1] = lane as u64;
686    input.v[2] = slice as u64;
687    input.v[3] = m_prime as u64;
688    input.v[4] = t as u64;
689    input.v[5] = argon_type as u64;
690    input.v[6] = counter;
691
692    let zero_block = Block::zero();
693    let mut tmp = Block::zero();
694    fill::fill_block_ref(&zero_block, &input, &mut tmp, false);
695    let mut addr_block = Block::zero();
696    fill::fill_block_ref(&zero_block, &tmp, &mut addr_block, false);
697    out.copy_from_slice(&addr_block.v);
698}
699
700/// Map J1 to a reference block index within the available set W.
701fn index_alpha(
702    pass: u32,
703    slice: u32,
704    _lanes: u32,
705    segment_length: u32,
706    index_in_segment: u32,
707    q: u32,
708    same_lane: bool,
709    j1: u32,
710) -> u32 {
711    // Determine reference area size
712    let reference_area_size = if pass == 0 {
713        // First pass: can only reference blocks already computed
714        if slice == 0 {
715            // Same lane, same slice, only previous blocks
716            index_in_segment.saturating_sub(1)
717        } else {
718            if same_lane {
719                slice * segment_length + index_in_segment - 1
720            } else {
721                slice * segment_length - if index_in_segment == 0 { 1 } else { 0 }
722            }
723        }
724    } else {
725        // Subsequent passes: all blocks except the current one
726        if same_lane {
727            q - segment_length + index_in_segment - 1
728        } else {
729            q - segment_length - if index_in_segment == 0 { 1 } else { 0 }
730        }
731    };
732
733    if reference_area_size == 0 {
734        return 0;
735    }
736
737    // Map J1 to an index with bias toward recent blocks
738    let j1_64 = j1 as u64;
739    let x = (j1_64 * j1_64) >> 32;
740    let y = (reference_area_size as u64 * x) >> 32;
741    let relative_position = (reference_area_size as u64 - 1 - y) as u32;
742
743    // Compute starting position
744    let start_position = if pass == 0 {
745        0
746    } else {
747        if slice == SYNC_POINTS - 1 {
748            0
749        } else {
750            (slice + 1) * segment_length
751        }
752    };
753
754    (start_position + relative_position) % q
755}
756
757/// Compute H_0 as defined in the RFC.
758#[cfg(feature = "alloc")]
759fn compute_h0(
760    argon_type: u32,
761    password: &[u8],
762    salt: &[u8],
763    secret: &[u8],
764    ad: &[u8],
765    p: u32,
766    tag_length: u32,
767    m: u32,
768    t: u32,
769) -> [u8; 64] {
770    let mut blake = Blake2b::new_keyed(&[], 64);
771
772    blake.update(&p.to_le_bytes());
773    blake.update(&tag_length.to_le_bytes());
774    blake.update(&m.to_le_bytes());
775    blake.update(&t.to_le_bytes());
776    blake.update(&VERSION.to_le_bytes());
777    blake.update(&argon_type.to_le_bytes());
778    blake.update(&(password.len() as u32).to_le_bytes());
779    blake.update(password);
780    blake.update(&(salt.len() as u32).to_le_bytes());
781    blake.update(salt);
782    blake.update(&(secret.len() as u32).to_le_bytes());
783    blake.update(secret);
784    blake.update(&(ad.len() as u32).to_le_bytes());
785    blake.update(ad);
786
787    let hash = blake.sum();
788    let mut result = [0u8; 64];
789    result.copy_from_slice(&hash.as_ref()[..64]);
790    result
791}
792
793/// Variable-length hash function H' as defined in RFC 9106 Section 3.3.
794///
795/// Uses Blake2b to fill `out` (its length is the tag length `T`) without
796/// performing any heap allocation.
797#[cfg(feature = "alloc")]
798fn variable_length_hash_into(input: &[u8], out: &mut [u8]) {
799    let tag_length = out.len();
800    debug_assert!(u32::try_from(tag_length).is_ok(), "tag length must fit in a u32");
801
802    if tag_length <= 64 {
803        // Short output: H'^T(A) = H^T(LE32(T)||A)
804        let mut blake = Blake2b::new_keyed(&[], tag_length);
805        blake.update(&(tag_length as u32).to_le_bytes());
806        blake.update(input);
807        let hash = blake.sum();
808        out.copy_from_slice(&hash.as_ref()[..tag_length]);
809        return;
810    }
811
812    // Long output
813    // r = ceil(T/32) - 2
814    let r = tag_length.div_ceil(32) - 2;
815
816    // V_1 = H^(64)(LE32(T)||A)
817    let mut v = [0u8; 64];
818    {
819        let mut blake = Blake2b::new_keyed(&[], 64);
820        blake.update(&(tag_length as u32).to_le_bytes());
821        blake.update(input);
822        let hash = blake.sum();
823        v.copy_from_slice(&hash.as_ref()[..64]);
824    }
825
826    // W_1 = first 32 bytes of V_1
827    out[..32].copy_from_slice(&v[..32]);
828
829    // V_2 through V_r
830    let mut offset = 32;
831    for _ in 2..=r {
832        let mut blake = Blake2b::new_keyed(&[], 64);
833        blake.update(&v);
834        let hash = blake.sum();
835        v.copy_from_slice(&hash.as_ref()[..64]);
836        out[offset..offset + 32].copy_from_slice(&v[..32]);
837        offset += 32;
838    }
839
840    // V_{r+1} = H^(T-32*r)(V_r)
841    let remaining = tag_length - 32 * r;
842    let mut blake = Blake2b::new_keyed(&[], remaining);
843    blake.update(&v);
844    let hash = blake.sum();
845    out[offset..offset + remaining].copy_from_slice(&hash.as_ref()[..remaining]);
846}
847
848// ============================================================
849// Compression function G and Permutation P
850// ============================================================
851
852/// Argon2's block arena, and the only place where memory unsafety lives.
853///
854/// Argon2 fills disjoint blocks concurrently while reading blocks that were
855/// finalized at an earlier synchronization point, an access pattern the borrow
856/// checker cannot describe directly. All of that unsafety is confined here:
857///
858/// * the arena is allocated uninitialized and written through a shared
859///   reference (every block is written before it is read);
860/// * [`Memory::get`] and [`Memory::get_mut`] hand out two shared borrows and a
861///   raw pointer to three pairwise-distinct blocks, so the kernel's reads and
862///   write do not alias;
863/// * lanes only ever write their own blocks, so concurrent calls from different
864///   lanes touch disjoint blocks.
865struct Memory {
866    blocks: Vec<MaybeUninit<Block>>,
867}
868
869// SAFETY: concurrent access is always disjoint. Every write targets the calling
870// lane's own current block, while reads only target blocks finalized at an
871// earlier synchronization point or this lane's own previous block. See
872// `fill_segment`.
873unsafe impl Send for Memory {}
874unsafe impl Sync for Memory {}
875
876impl Memory {
877    /// Allocate an uninitialized arena of `len` blocks.
878    ///
879    /// The contents are left uninitialized on purpose: Argon2 writes every
880    /// block before reading it, so zero-filling the arena would be a wasted
881    /// pass over the whole allocation.
882    fn uninit(len: usize) -> Self {
883        let mut blocks: Vec<MaybeUninit<Block>> = Vec::with_capacity(len);
884        // SAFETY: `Block` is plain-old-data (an array of `u64`), so every bit
885        // pattern is valid, and `MaybeUninit` tolerates uninitialized contents.
886        // The length is set to the capacity that was just reserved.
887        unsafe { blocks.set_len(len) };
888        Memory {
889            blocks,
890        }
891    }
892
893    /// Base pointer of the arena.
894    #[inline(always)]
895    fn base_ptr(&self) -> *mut Block {
896        self.blocks.as_ptr() as *mut Block
897    }
898
899    /// Return the number of blocks in the arena.
900    #[inline(always)]
901    fn len(&self) -> usize {
902        self.blocks.len()
903    }
904
905    /// Write `block` to `index`.
906    ///
907    /// The block at `index` may be uninitialized beforehand; its previous
908    /// contents are not read.
909    #[inline(always)]
910    fn write(&self, index: usize, block: Block) {
911        debug_assert!(index < self.len());
912        // SAFETY: `index` is in bounds and no other access to that block is
913        // live, so the write does not alias.
914        unsafe { core::ptr::write(self.base_ptr().add(index), block) };
915    }
916
917    /// Read the block at `index`.
918    ///
919    /// Callers must only read blocks that have already been written and are not
920    /// concurrently written.
921    #[inline(always)]
922    fn get(&self, index: usize) -> &Block {
923        debug_assert!(index < self.len());
924        // SAFETY: `index` is in bounds, the block has been initialized, and it
925        // is not concurrently written (see the type-level invariant).
926        unsafe { &*self.base_ptr().add(index) }
927    }
928
929    /// First word of the block at `index` (used by Argon2d addressing).
930    #[inline(always)]
931    fn first_word(&self, index: usize) -> u64 {
932        self.get(index).v[0]
933    }
934
935    /// Raw pointer to the block at `index`, without dereferencing it.
936    ///
937    /// This may point at an uninitialized block (on the first pass) and is
938    /// handed to the compression kernel, which is responsible for writing it.
939    #[inline(always)]
940    fn get_mut(&self, index: usize) -> *mut Block {
941        debug_assert!(index < self.len());
942        // SAFETY: `index` is in bounds, so the offset stays within the
943        // allocation. The returned pointer is not dereferenced here.
944        unsafe { self.base_ptr().add(index) }
945    }
946}
947
948// ============================================================
949// Base64 helpers (PHC format uses standard base64 without padding)
950// ============================================================
951
952#[cfg(feature = "alloc")]
953fn base64_encode_no_pad(input: &[u8]) -> String {
954    base64::encode(input, base64::Alphabet::StandardNoPadding)
955}
956
957#[cfg(feature = "alloc")]
958fn base64_decode_no_pad(input: &str) -> Result<Vec<u8>, ()> {
959    base64::decode(input.as_bytes(), base64::Alphabet::StandardNoPadding).map_err(|_| ())
960}
961
962#[cfg(feature = "alloc")]
963fn parse_param(s: &str, prefix: &str) -> Result<u32, Argon2Error> {
964    if !s.starts_with(prefix) {
965        return Err(Argon2Error::InvalidEncoding("invalid parameter prefix"));
966    }
967    s[prefix.len()..]
968        .parse::<u32>()
969        .map_err(|_| Argon2Error::InvalidEncoding("invalid parameter value"))
970}
971
972// ============================================================
973// Tests
974// ============================================================
975
976#[cfg(test)]
977mod tests {
978    use super::*;
979
980    fn derive_key_typed(
981        argon_type: u32,
982        password: &[u8],
983        salt: &[u8],
984        secret: &[u8],
985        ad: &[u8],
986        iterations: u32,
987        memory: u32,
988        parallelism: u32,
989        tag_length: u32,
990    ) -> Vec<u8> {
991        let params = Params {
992            iterations: iterations,
993            memory: memory,
994            parallelism: parallelism,
995        };
996        let mut out = vec![0u8; tag_length as usize];
997        argon2_core(argon_type, password, salt, secret, ad, &params, &mut out).unwrap();
998        out
999    }
1000
1001    /// Like `derive_key_typed`, but with an explicit backend.
1002    #[allow(clippy::too_many_arguments)]
1003    fn derive_key_typed_backend(
1004        backend: Backend,
1005        argon_type: u32,
1006        password: &[u8],
1007        salt: &[u8],
1008        secret: &[u8],
1009        ad: &[u8],
1010        iterations: u32,
1011        memory: u32,
1012        parallelism: u32,
1013        tag_length: u32,
1014    ) -> Vec<u8> {
1015        let params = Params {
1016            iterations,
1017            memory,
1018            parallelism,
1019        };
1020        let mut out = vec![0u8; tag_length as usize];
1021        argon2_core_with_backend(argon_type, password, salt, secret, ad, &params, &mut out, backend).unwrap();
1022        out
1023    }
1024
1025    /// Every backend compiled into this build and actually supported by the
1026    /// running CPU.
1027    fn available_backends() -> Vec<Backend> {
1028        let mut backends = vec![Backend::Scalar];
1029
1030        #[cfg(target_arch = "aarch64")]
1031        {
1032            backends.push(Backend::Neon);
1033            #[cfg(feature = "std")]
1034            if std::arch::is_aarch64_feature_detected!("sha3") {
1035                backends.push(Backend::NeonSha3);
1036            }
1037        }
1038
1039        #[cfg(target_arch = "x86_64")]
1040        {
1041            #[cfg(feature = "std")]
1042            if std::arch::is_x86_feature_detected!("avx2") {
1043                backends.push(Backend::Avx2);
1044            }
1045        }
1046
1047        backends
1048    }
1049
1050    /// Every SIMD backend must reproduce the scalar kernel bit-for-bit, for
1051    /// all three Argon2 types, multiple passes and multiple lanes.
1052    #[test]
1053    fn test_backends_match_reference() {
1054        let password = b"password";
1055        let salt = b"somesalt";
1056        for backend in available_backends() {
1057            for v in GO_VECTORS.iter() {
1058                let expected = hex::decode(v.hash).unwrap();
1059                let result = derive_key_typed_backend(
1060                    backend,
1061                    v.mode,
1062                    password,
1063                    salt,
1064                    &[],
1065                    &[],
1066                    v.time,
1067                    v.memory,
1068                    v.threads,
1069                    expected.len() as u32,
1070                );
1071                assert_eq!(
1072                    result, expected,
1073                    "backend {:?} failed Go vector (mode={}, t={}, m={}, p={})",
1074                    backend, v.mode, v.time, v.memory, v.threads
1075                );
1076            }
1077        }
1078    }
1079
1080    /// Convenience wrapper around `derive_key` that returns an allocated tag.
1081    fn derive_key_vec(
1082        tag_length: usize,
1083        password: &[u8],
1084        salt: &[u8],
1085        secret: &[u8],
1086        ad: &[u8],
1087        params: &Params,
1088    ) -> Vec<u8> {
1089        let mut out = vec![0u8; tag_length];
1090        derive_key(&mut out, password, salt, secret, ad, params).unwrap();
1091        out
1092    }
1093
1094    /// Convenience wrapper around `variable_length_hash_into` returning a Vec.
1095    fn variable_length_hash_vec(input: &[u8], tag_length: usize) -> Vec<u8> {
1096        let mut out = vec![0u8; tag_length];
1097        variable_length_hash_into(input, &mut out);
1098        out
1099    }
1100
1101    // ================================================================
1102    // RFC 9106 Section 5 test vectors
1103    // password = 0x01*32, salt = 0x02*16, secret = 0x03*8, ad = 0x04*12
1104    // t=3, m=32, p=4, tag=32
1105    // ================================================================
1106
1107    #[test]
1108    fn test_rfc9106_argon2d() {
1109        let pwd = vec![0x01u8; 32];
1110        let salt = vec![0x02u8; 16];
1111        let secret = vec![0x03u8; 8];
1112        let ad = vec![0x04u8; 12];
1113        let expected = hex::decode("512b391b6f1162975371d30919734294f868e3be3984f3c1a13a4db9fabe4acb").unwrap();
1114        let result = derive_key_typed(ARGON2D, &pwd, &salt, &secret, &ad, 3, 32, 4, 32);
1115        assert_eq!(result, expected);
1116    }
1117
1118    #[test]
1119    fn test_rfc9106_argon2i() {
1120        let pwd = vec![0x01u8; 32];
1121        let salt = vec![0x02u8; 16];
1122        let secret = vec![0x03u8; 8];
1123        let ad = vec![0x04u8; 12];
1124        let expected = hex::decode("c814d9d1dc7f37aa13f0d77f2494bda1c8de6b016dd388d29952a4c4672b6ce8").unwrap();
1125        let result = derive_key_typed(ARGON2I, &pwd, &salt, &secret, &ad, 3, 32, 4, 32);
1126        assert_eq!(result, expected);
1127    }
1128
1129    // ================================================================
1130    // RFC 9106 H_0 pre-hashing digest tests for all types
1131    // ================================================================
1132    // Pre-hashing digest test (H0 from RFC 9106 Section 5.3)
1133    // ================================================================
1134
1135    #[test]
1136    fn test_h0() {
1137        let pwd = vec![0x01u8; 32];
1138        let salt = vec![0x02u8; 16];
1139        let secret = vec![0x03u8; 8];
1140        let ad = vec![0x04u8; 12];
1141        let h0 = compute_h0(ARGON2ID, &pwd, &salt, &secret, &ad, 4, 32, 32, 3);
1142        let expected = "2889de487eb42ae500c0007ed9252f1069eadec40d5765b485de6dc2437a67b8546a2f0acc1a0882db8fcf74714b472e94df421a5da1112ffa11434370a1e997";
1143        assert_eq!(hex::encode(h0), expected);
1144    }
1145
1146    // ================================================================
1147    // Test vectors from golang.org/x/crypto/argon2
1148    // password = "password", salt = "somesalt", no secret, no AD
1149    // ================================================================
1150
1151    struct Vec3 {
1152        mode: u32,
1153        time: u32,
1154        memory: u32,
1155        threads: u32,
1156        hash: &'static str,
1157    }
1158
1159    const GO_VECTORS: &[Vec3] = &[
1160        Vec3 {
1161            mode: ARGON2I,
1162            time: 1,
1163            memory: 64,
1164            threads: 1,
1165            hash: "b9c401d1844a67d50eae3967dc28870b22e508092e861a37",
1166        },
1167        Vec3 {
1168            mode: ARGON2D,
1169            time: 1,
1170            memory: 64,
1171            threads: 1,
1172            hash: "8727405fd07c32c78d64f547f24150d3f2e703a89f981a19",
1173        },
1174        Vec3 {
1175            mode: ARGON2ID,
1176            time: 1,
1177            memory: 64,
1178            threads: 1,
1179            hash: "655ad15eac652dc59f7170a7332bf49b8469be1fdb9c28bb",
1180        },
1181        Vec3 {
1182            mode: ARGON2I,
1183            time: 2,
1184            memory: 64,
1185            threads: 1,
1186            hash: "8cf3d8f76a6617afe35fac48eb0b7433a9a670ca4a07ed64",
1187        },
1188        Vec3 {
1189            mode: ARGON2D,
1190            time: 2,
1191            memory: 64,
1192            threads: 1,
1193            hash: "3be9ec79a69b75d3752acb59a1fbb8b295a46529c48fbb75",
1194        },
1195        Vec3 {
1196            mode: ARGON2ID,
1197            time: 2,
1198            memory: 64,
1199            threads: 1,
1200            hash: "068d62b26455936aa6ebe60060b0a65870dbfa3ddf8d41f7",
1201        },
1202        Vec3 {
1203            mode: ARGON2I,
1204            time: 2,
1205            memory: 64,
1206            threads: 2,
1207            hash: "2089f3e78a799720f80af806553128f29b132cafe40d059f",
1208        },
1209        Vec3 {
1210            mode: ARGON2D,
1211            time: 2,
1212            memory: 64,
1213            threads: 2,
1214            hash: "68e2462c98b8bc6bb60ec68db418ae2c9ed24fc6748a40e9",
1215        },
1216        Vec3 {
1217            mode: ARGON2ID,
1218            time: 2,
1219            memory: 64,
1220            threads: 2,
1221            hash: "350ac37222f436ccb5c0972f1ebd3bf6b958bf2071841362",
1222        },
1223        Vec3 {
1224            mode: ARGON2I,
1225            time: 3,
1226            memory: 256,
1227            threads: 2,
1228            hash: "f5bbf5d4c3836af13193053155b73ec7476a6a2eb93fd5e6",
1229        },
1230        Vec3 {
1231            mode: ARGON2D,
1232            time: 3,
1233            memory: 256,
1234            threads: 2,
1235            hash: "f4f0669218eaf3641f39cc97efb915721102f4b128211ef2",
1236        },
1237        Vec3 {
1238            mode: ARGON2ID,
1239            time: 3,
1240            memory: 256,
1241            threads: 2,
1242            hash: "4668d30ac4187e6878eedeacf0fd83c5a0a30db2cc16ef0b",
1243        },
1244        Vec3 {
1245            mode: ARGON2I,
1246            time: 4,
1247            memory: 4096,
1248            threads: 4,
1249            hash: "a11f7b7f3f93f02ad4bddb59ab62d121e278369288a0d0e7",
1250        },
1251        Vec3 {
1252            mode: ARGON2D,
1253            time: 4,
1254            memory: 4096,
1255            threads: 4,
1256            hash: "935598181aa8dc2b720914aa6435ac8d3e3a4210c5b0fb2d",
1257        },
1258        Vec3 {
1259            mode: ARGON2ID,
1260            time: 4,
1261            memory: 4096,
1262            threads: 4,
1263            hash: "145db9733a9f4ee43edf33c509be96b934d505a4efb33c5a",
1264        },
1265        Vec3 {
1266            mode: ARGON2I,
1267            time: 4,
1268            memory: 1024,
1269            threads: 8,
1270            hash: "0cdd3956aa35e6b475a7b0c63488822f774f15b43f6e6e17",
1271        },
1272        Vec3 {
1273            mode: ARGON2D,
1274            time: 4,
1275            memory: 1024,
1276            threads: 8,
1277            hash: "83604fc2ad0589b9d055578f4d3cc55bc616df3578a896e9",
1278        },
1279        Vec3 {
1280            mode: ARGON2ID,
1281            time: 4,
1282            memory: 1024,
1283            threads: 8,
1284            hash: "8dafa8e004f8ea96bf7c0f93eecf67a6047476143d15577f",
1285        },
1286        Vec3 {
1287            mode: ARGON2I,
1288            time: 2,
1289            memory: 64,
1290            threads: 3,
1291            hash: "5cab452fe6b8479c8661def8cd703b611a3905a6d5477fe6",
1292        },
1293        Vec3 {
1294            mode: ARGON2D,
1295            time: 2,
1296            memory: 64,
1297            threads: 3,
1298            hash: "22474a423bda2ccd36ec9afd5119e5c8949798cadf659f51",
1299        },
1300        Vec3 {
1301            mode: ARGON2ID,
1302            time: 2,
1303            memory: 64,
1304            threads: 3,
1305            hash: "4a15b31aec7c2590b87d1f520be7d96f56658172deaa3079",
1306        },
1307        Vec3 {
1308            mode: ARGON2I,
1309            time: 3,
1310            memory: 1024,
1311            threads: 6,
1312            hash: "d236b29c2b2a09babee842b0dec6aa1e83ccbdea8023dced",
1313        },
1314        Vec3 {
1315            mode: ARGON2D,
1316            time: 3,
1317            memory: 1024,
1318            threads: 6,
1319            hash: "a3351b0319a53229152023d9206902f4ef59661cdca89481",
1320        },
1321        Vec3 {
1322            mode: ARGON2ID,
1323            time: 3,
1324            memory: 1024,
1325            threads: 6,
1326            hash: "1640b932f4b60e272f5d2207b9a9c626ffa1bd88d2349016",
1327        },
1328    ];
1329
1330    #[test]
1331    fn test_go_vectors() {
1332        let password = b"password";
1333        let salt = b"somesalt";
1334        for (i, v) in GO_VECTORS.iter().enumerate() {
1335            let expected = hex::decode(v.hash).unwrap();
1336            let result = derive_key_typed(
1337                v.mode,
1338                password,
1339                salt,
1340                &[],
1341                &[],
1342                v.time,
1343                v.memory,
1344                v.threads,
1345                expected.len() as u32,
1346            );
1347            assert_eq!(
1348                result, expected,
1349                "Go vector {} failed (mode={}, t={}, m={}, p={})",
1350                i, v.mode, v.time, v.memory, v.threads
1351            );
1352        }
1353    }
1354
1355    // ================================================================
1356    // Test vectors from the C reference implementation (phc-winner-argon2)
1357    // https://github.com/P-H-C/phc-winner-argon2/blob/master/src/test.c
1358    // All use password="password", salt="somesalt" unless noted, v=19
1359    // ================================================================
1360
1361    struct CVector {
1362        mode: u32,
1363        time: u32,
1364        memory: u32,
1365        threads: u32,
1366        hash: &'static str,
1367        pwd: &'static str,
1368        slt: &'static str,
1369    }
1370
1371    const C_VECTORS: &[CVector] = &[
1372        CVector {
1373            mode: ARGON2I,
1374            time: 2,
1375            memory: 65536,
1376            threads: 1,
1377            hash: "c1628832147d9720c5bd1cfd61367078729f6dfb6f8fea9ff98158e0d7816ed0",
1378            pwd: "password",
1379            slt: "somesalt",
1380        },
1381        CVector {
1382            mode: ARGON2I,
1383            time: 2,
1384            memory: 262144,
1385            threads: 1,
1386            hash: "296dbae80b807cdceaad44ae741b506f14db0959267b183b118f9b24229bc7cb",
1387            pwd: "password",
1388            slt: "somesalt",
1389        },
1390        CVector {
1391            mode: ARGON2I,
1392            time: 2,
1393            memory: 256,
1394            threads: 1,
1395            hash: "89e9029f4637b295beb027056a7336c414fadd43f6b208645281cb214a56452f",
1396            pwd: "password",
1397            slt: "somesalt",
1398        },
1399        CVector {
1400            mode: ARGON2I,
1401            time: 2,
1402            memory: 256,
1403            threads: 2,
1404            hash: "4ff5ce2769a1d7f4c8a491df09d41a9fbe90e5eb02155a13e4c01e20cd4eab61",
1405            pwd: "password",
1406            slt: "somesalt",
1407        },
1408        CVector {
1409            mode: ARGON2I,
1410            time: 1,
1411            memory: 65536,
1412            threads: 1,
1413            hash: "d168075c4d985e13ebeae560cf8b94c3b5d8a16c51916b6f4ac2da3ac11bbecf",
1414            pwd: "password",
1415            slt: "somesalt",
1416        },
1417        CVector {
1418            mode: ARGON2I,
1419            time: 4,
1420            memory: 65536,
1421            threads: 1,
1422            hash: "aaa953d58af3706ce3df1aefd4a64a84e31d7f54175231f1285259f88174ce5b",
1423            pwd: "password",
1424            slt: "somesalt",
1425        },
1426        CVector {
1427            mode: ARGON2I,
1428            time: 2,
1429            memory: 65536,
1430            threads: 1,
1431            hash: "14ae8da01afea8700c2358dcef7c5358d9021282bd88663a4562f59fb74d22ee",
1432            pwd: "differentpassword",
1433            slt: "somesalt",
1434        },
1435        CVector {
1436            mode: ARGON2I,
1437            time: 2,
1438            memory: 65536,
1439            threads: 1,
1440            hash: "b0357cccfbef91f3860b0dba447b2348cbefecadaf990abfe9cc40726c521271",
1441            pwd: "password",
1442            slt: "diffsalt",
1443        },
1444        CVector {
1445            mode: ARGON2ID,
1446            time: 2,
1447            memory: 65536,
1448            threads: 1,
1449            hash: "09316115d5cf24ed5a15a31a3ba326e5cf32edc24702987c02b6566f61913cf7",
1450            pwd: "password",
1451            slt: "somesalt",
1452        },
1453        CVector {
1454            mode: ARGON2ID,
1455            time: 2,
1456            memory: 262144,
1457            threads: 1,
1458            hash: "78fe1ec91fb3aa5657d72e710854e4c3d9b9198c742f9616c2f085bed95b2e8c",
1459            pwd: "password",
1460            slt: "somesalt",
1461        },
1462        CVector {
1463            mode: ARGON2ID,
1464            time: 2,
1465            memory: 256,
1466            threads: 1,
1467            hash: "9dfeb910e80bad0311fee20f9c0e2b12c17987b4cac90c2ef54d5b3021c68bfe",
1468            pwd: "password",
1469            slt: "somesalt",
1470        },
1471        CVector {
1472            mode: ARGON2ID,
1473            time: 2,
1474            memory: 256,
1475            threads: 2,
1476            hash: "6d093c501fd5999645e0ea3bf620d7b8be7fd2db59c20d9fff9539da2bf57037",
1477            pwd: "password",
1478            slt: "somesalt",
1479        },
1480        CVector {
1481            mode: ARGON2ID,
1482            time: 1,
1483            memory: 65536,
1484            threads: 1,
1485            hash: "f6a5adc1ba723dddef9b5ac1d464e180fcd9dffc9d1cbf76cca2fed795d9ca98",
1486            pwd: "password",
1487            slt: "somesalt",
1488        },
1489        CVector {
1490            mode: ARGON2ID,
1491            time: 4,
1492            memory: 65536,
1493            threads: 1,
1494            hash: "9025d48e68ef7395cca9079da4c4ec3affb3c8911fe4f86d1a2520856f63172c",
1495            pwd: "password",
1496            slt: "somesalt",
1497        },
1498        CVector {
1499            mode: ARGON2ID,
1500            time: 2,
1501            memory: 65536,
1502            threads: 1,
1503            hash: "0b84d652cf6b0c4beaef0dfe278ba6a80df6696281d7e0d2891b817d8c458fde",
1504            pwd: "differentpassword",
1505            slt: "somesalt",
1506        },
1507        CVector {
1508            mode: ARGON2ID,
1509            time: 2,
1510            memory: 65536,
1511            threads: 1,
1512            hash: "bdf32b05ccc42eb15d58fd19b1f856b113da1e9a5874fdcc544308565aa8141c",
1513            pwd: "password",
1514            slt: "diffsalt",
1515        },
1516    ];
1517
1518    #[test]
1519    fn test_c_reference_vectors() {
1520        for (i, v) in C_VECTORS.iter().enumerate() {
1521            let expected = hex::decode(v.hash).unwrap();
1522            let result = derive_key_typed(
1523                v.mode,
1524                v.pwd.as_bytes(),
1525                v.slt.as_bytes(),
1526                &[],
1527                &[],
1528                v.time,
1529                v.memory,
1530                v.threads,
1531                expected.len() as u32,
1532            );
1533            assert_eq!(
1534                result, expected,
1535                "C ref vector {} failed (mode={}, t={}, m={}, p={})",
1536                i, v.mode, v.time, v.memory, v.threads
1537            );
1538        }
1539    }
1540
1541    // ================================================================
1542    // PHC string format tests
1543    // ================================================================
1544
1545    #[test]
1546    fn test_phc_encode_decode() {
1547        let params = Params {
1548            iterations: 3,
1549            memory: 65536,
1550            parallelism: 4,
1551        };
1552        let salt = b"somesalt12345678";
1553        let tag = vec![0xAB; 32];
1554        let encoded = encode_phc(&params, salt, &tag);
1555        assert!(encoded.starts_with("$argon2id$v=19$m=65536,t=3,p=4$"));
1556        let (dp, ds, dt) = decode_phc(&encoded).unwrap();
1557        assert_eq!(dp.iterations, 3);
1558        assert_eq!(dp.memory, 65536);
1559        assert_eq!(dp.parallelism, 4);
1560        assert_eq!(ds, salt);
1561        assert_eq!(dt, tag);
1562    }
1563
1564    #[test]
1565    fn test_hash_and_verify() {
1566        let password = b"correct horse battery staple";
1567        let salt = b"randomsalt123456";
1568        let params = Params {
1569            iterations: 1,
1570            memory: 64,
1571            parallelism: 1,
1572        };
1573        let encoded = hash_password(password, salt, &params).unwrap();
1574        assert!(verify_password(password, &encoded, None).is_ok());
1575        assert_eq!(
1576            verify_password(b"wrong password", &encoded, None),
1577            Err(Argon2Error::VerifyMismatch)
1578        );
1579    }
1580
1581    #[test]
1582    fn test_decode_phc_invalid() {
1583        assert!(decode_phc("").is_err());
1584        assert!(decode_phc("$argon2i$v=19$m=4096,t=3,p=1$salt$hash").is_err());
1585        assert!(decode_phc("$argon2id$v=16$m=4096,t=3,p=1$salt$hash").is_err());
1586        assert!(decode_phc("not a phc string").is_err());
1587    }
1588
1589    #[test]
1590    fn test_decode_phc_rejects_invalid_params() {
1591        let salt = b"salt12345678";
1592        let tag = [0u8; 32];
1593
1594        // iterations must be >= 1
1595        let enc = encode_phc(
1596            &Params {
1597                iterations: 0,
1598                memory: 64,
1599                parallelism: 1,
1600            },
1601            salt,
1602            &tag,
1603        );
1604        assert!(matches!(decode_phc(&enc), Err(Argon2Error::InvalidParams(_))));
1605
1606        // parallelism must be >= 1
1607        let enc = encode_phc(
1608            &Params {
1609                iterations: 1,
1610                memory: 64,
1611                parallelism: 0,
1612            },
1613            salt,
1614            &tag,
1615        );
1616        assert!(matches!(decode_phc(&enc), Err(Argon2Error::InvalidParams(_))));
1617
1618        // parallelism must be <= 2^24 - 1
1619        let enc = encode_phc(
1620            &Params {
1621                iterations: 1,
1622                memory: 64,
1623                parallelism: u32::MAX,
1624            },
1625            salt,
1626            &tag,
1627        );
1628        assert!(matches!(decode_phc(&enc), Err(Argon2Error::InvalidParams(_))));
1629
1630        // memory must be >= 8*parallelism
1631        let enc = encode_phc(
1632            &Params {
1633                iterations: 1,
1634                memory: 4,
1635                parallelism: 1,
1636            },
1637            salt,
1638            &tag,
1639        );
1640        assert!(matches!(decode_phc(&enc), Err(Argon2Error::InvalidParams(_))));
1641
1642        // tag must be at least 4 bytes
1643        let enc = encode_phc(
1644            &Params {
1645                iterations: 1,
1646                memory: 64,
1647                parallelism: 1,
1648            },
1649            salt,
1650            &[0u8; 3],
1651        );
1652        assert!(matches!(decode_phc(&enc), Err(Argon2Error::InvalidParams(_))));
1653    }
1654
1655    #[test]
1656    fn test_decode_phc_huge_parallelism_does_not_panic() {
1657        let salt = b"salt12345678";
1658        let tag = [0u8; 32];
1659        // Before the fix, `8 * parallelism` overflowed in debug builds (panic)
1660        // and wrapped in release builds (multi-terabyte allocation).
1661        let enc = encode_phc(
1662            &Params {
1663                iterations: 1,
1664                memory: u32::MAX,
1665                parallelism: u32::MAX,
1666            },
1667            salt,
1668            &tag,
1669        );
1670        assert!(decode_phc(&enc).is_err());
1671        assert!(verify_password(b"password", &enc, None).is_err());
1672    }
1673
1674    #[test]
1675    fn test_verify_password_limits() {
1676        let password = b"correct horse battery staple";
1677        let salt = b"randomsalt123456";
1678        let params = Params {
1679            iterations: 2,
1680            memory: 64,
1681            parallelism: 1,
1682        };
1683        let encoded = hash_password(password, salt, &params).unwrap();
1684
1685        // No limits: normal verification.
1686        assert!(verify_password(password, &encoded, None).is_ok());
1687
1688        // Limits exactly equal to the hash's parameters still verify.
1689        assert!(verify_password(password, &encoded, Some(&params)).is_ok());
1690
1691        // Any tighter bound rejects the hash before deriving.
1692        for limits in [
1693            Params {
1694                iterations: 1,
1695                memory: 64,
1696                parallelism: 1,
1697            },
1698            Params {
1699                iterations: 2,
1700                memory: 32,
1701                parallelism: 1,
1702            },
1703            Params {
1704                iterations: 2,
1705                memory: 64,
1706                parallelism: 0,
1707            },
1708        ] {
1709            assert!(matches!(
1710                verify_password(password, &encoded, Some(&limits)),
1711                Err(Argon2Error::InvalidParams(_))
1712            ));
1713        }
1714    }
1715
1716    #[test]
1717    fn test_verify_password_limits_reject_before_allocating() {
1718        let salt = b"salt12345678";
1719        let tag = [0u8; 32];
1720        // A hostile hash requesting ~4 GiB of memory. The limit check must
1721        // reject it before `derive_key` allocates anything.
1722        let hostile = encode_phc(
1723            &Params {
1724                iterations: 1,
1725                memory: u32::MAX,
1726                parallelism: 1,
1727            },
1728            salt,
1729            &tag,
1730        );
1731        let limits = Params {
1732            iterations: 1,
1733            memory: 64,
1734            parallelism: 1,
1735        };
1736        assert!(matches!(
1737            verify_password(b"password", &hostile, Some(&limits)),
1738            Err(Argon2Error::InvalidParams(_))
1739        ));
1740    }
1741
1742    #[cfg(target_pointer_width = "64")]
1743    #[test]
1744    fn test_check_u32_len() {
1745        assert!(check_u32_len(usize::MAX, "too long").is_err());
1746        assert!(check_u32_len(u32::MAX as usize, "ok").is_ok());
1747        assert!(check_u32_len(0, "ok").is_ok());
1748    }
1749
1750    #[test]
1751    fn test_invalid_params() {
1752        let mut out = [0u8; 32];
1753        assert!(
1754            derive_key(
1755                &mut out,
1756                b"password",
1757                b"salt",
1758                &[],
1759                &[],
1760                &Params {
1761                    iterations: 0,
1762                    memory: 64,
1763                    parallelism: 1
1764                }
1765            )
1766            .is_err()
1767        );
1768        assert!(
1769            derive_key(
1770                &mut out,
1771                b"password",
1772                b"salt",
1773                &[],
1774                &[],
1775                &Params {
1776                    iterations: 1,
1777                    memory: 4,
1778                    parallelism: 1
1779                }
1780            )
1781            .is_err()
1782        );
1783        // The output buffer must be at least 4 bytes long.
1784        let mut short = [0u8; 3];
1785        assert!(
1786            derive_key(
1787                &mut short,
1788                b"password",
1789                b"salt",
1790                &[],
1791                &[],
1792                &Params {
1793                    iterations: 1,
1794                    memory: 64,
1795                    parallelism: 1
1796                }
1797            )
1798            .is_err()
1799        );
1800    }
1801
1802    #[test]
1803    fn test_variable_length_hash_short() {
1804        let input = b"test input";
1805        let r32 = variable_length_hash_vec(input, 32);
1806        assert_eq!(r32.len(), 32);
1807        assert_eq!(variable_length_hash_vec(input, 32), r32);
1808        let r48 = variable_length_hash_vec(input, 48);
1809        assert_eq!(r48.len(), 48);
1810        assert_ne!(&r32[..], &r48[..32]);
1811    }
1812
1813    #[test]
1814    fn test_variable_length_hash_long() {
1815        assert_eq!(variable_length_hash_vec(b"test input for long hash", 128).len(), 128);
1816        assert_eq!(variable_length_hash_vec(b"test input for long hash", 1024).len(), 1024);
1817    }
1818
1819    #[test]
1820    fn test_argon2id_min_memory() {
1821        let result = derive_key_vec(
1822            32,
1823            b"password",
1824            b"saltsalt",
1825            &[],
1826            &[],
1827            &Params {
1828                iterations: 1,
1829                memory: 8,
1830                parallelism: 1,
1831            },
1832        );
1833        assert_eq!(result.len(), 32);
1834    }
1835
1836    #[test]
1837    fn test_argon2id_multiple_lanes() {
1838        let result = derive_key_vec(
1839            32,
1840            b"password",
1841            b"saltsaltsaltsalt",
1842            &[],
1843            &[],
1844            &Params {
1845                iterations: 1,
1846                memory: 64,
1847                parallelism: 4,
1848            },
1849        );
1850        assert_eq!(result.len(), 32);
1851    }
1852
1853    #[test]
1854    fn test_different_passwords() {
1855        let p = Params {
1856            iterations: 1,
1857            memory: 64,
1858            parallelism: 1,
1859        };
1860        assert_ne!(
1861            derive_key_vec(32, b"password1", b"saltsaltsaltsalt", &[], &[], &p),
1862            derive_key_vec(32, b"password2", b"saltsaltsaltsalt", &[], &[], &p)
1863        );
1864    }
1865
1866    #[test]
1867    fn test_different_salts() {
1868        let p = Params {
1869            iterations: 1,
1870            memory: 64,
1871            parallelism: 1,
1872        };
1873        assert_ne!(
1874            derive_key_vec(32, b"password", b"salt1234salt1234", &[], &[], &p),
1875            derive_key_vec(32, b"password", b"salt5678salt5678", &[], &[], &p)
1876        );
1877    }
1878
1879    #[test]
1880    fn test_long_tag() {
1881        let result = derive_key_vec(
1882            64,
1883            b"password",
1884            b"saltsaltsaltsalt",
1885            &[],
1886            &[],
1887            &Params {
1888                iterations: 1,
1889                memory: 64,
1890                parallelism: 1,
1891            },
1892        );
1893        assert_eq!(result.len(), 64);
1894    }
1895
1896    #[test]
1897    fn test_phc_roundtrip() {
1898        let password = b"password";
1899        let salt = b"somesalt";
1900        let params = Params {
1901            iterations: 1,
1902            memory: 64,
1903            parallelism: 1,
1904        };
1905        let tag = derive_key_vec(24, password, salt, &[], &[], &params);
1906        let encoded = encode_phc(&params, salt, &tag);
1907        let (dp, ds, dt) = decode_phc(&encoded).unwrap();
1908        assert_eq!(dp.memory, params.memory);
1909        assert_eq!(dp.iterations, params.iterations);
1910        assert_eq!(dp.parallelism, params.parallelism);
1911        assert_eq!(ds, salt);
1912        assert_eq!(dt, tag);
1913    }
1914
1915    // ================================================================
1916    // RFC 9106 intermediate block verification
1917    // Verifies Block 0000 and Block 0031 after each pass for all 3 types
1918    // Parameters: pwd=0x01*32, salt=0x02*16, secret=0x03*8, ad=0x04*12
1919    //             t=3, m=32, p=4, tag=32
1920    // ================================================================
1921
1922    fn argon2_core_with_passes(
1923        argon_type: u32,
1924        password: &[u8],
1925        salt: &[u8],
1926        secret: &[u8],
1927        ad: &[u8],
1928        params: &Params,
1929    ) -> Vec<Vec<Block>> {
1930        let p = params.parallelism;
1931        let t = params.iterations;
1932        let m = params.memory;
1933        let tag_length = 32u32;
1934
1935        let h0 = compute_h0(argon_type, password, salt, secret, ad, p, tag_length, m, t);
1936        let m_prime = 4 * p * (m / (4 * p));
1937        let q = m_prime / p;
1938
1939        let mem = Memory::uninit(m_prime as usize);
1940
1941        let mut input = [0u8; 72];
1942        input[..64].copy_from_slice(&h0);
1943        let mut block_bytes = [0u8; BLOCK_SIZE];
1944        for i in 0..p {
1945            input[68..72].copy_from_slice(&i.to_le_bytes());
1946
1947            input[64..68].copy_from_slice(&0u32.to_le_bytes());
1948            variable_length_hash_into(&input, &mut block_bytes);
1949            mem.write((i * q) as usize, Block::from_bytes(&block_bytes));
1950
1951            input[64..68].copy_from_slice(&1u32.to_le_bytes());
1952            variable_length_hash_into(&input, &mut block_bytes);
1953            mem.write((i * q + 1) as usize, Block::from_bytes(&block_bytes));
1954        }
1955
1956        let mut pass_snapshots = Vec::new();
1957        for pass in 0..t {
1958            for slice in 0..SYNC_POINTS {
1959                for lane in 0..p {
1960                    fill_segment(Backend::Scalar, &mem, argon_type, pass, lane, slice, p, q, t, m_prime);
1961                }
1962            }
1963            pass_snapshots.push((0..mem.len()).map(|i| mem.get(i).clone()).collect());
1964        }
1965
1966        pass_snapshots
1967    }
1968
1969    fn block0_word(block: &Block, idx: usize) -> String {
1970        format!("{:016x}", block.v[idx])
1971    }
1972
1973    fn block_last_word(block: &Block, idx: usize) -> String {
1974        format!("{:016x}", block.v[idx])
1975    }
1976
1977    #[test]
1978    fn test_rfc9106_argon2d_intermediate_blocks() {
1979        let pwd = vec![0x01u8; 32];
1980        let salt = vec![0x02u8; 16];
1981        let secret = vec![0x03u8; 8];
1982        let ad = vec![0x04u8; 12];
1983        let params = Params {
1984            iterations: 3,
1985            memory: 32,
1986            parallelism: 4,
1987        };
1988        let passes = argon2_core_with_passes(ARGON2D, &pwd, &salt, &secret, &ad, &params);
1989
1990        let p = params.parallelism;
1991        let q = (4 * p * (params.memory / (4 * p))) / p;
1992        let m_prime = p * q;
1993
1994        assert_eq!(block0_word(&passes[0][0], 0), "db2fea6b2c6f5c8a");
1995        assert_eq!(block_last_word(&passes[0][(m_prime - 1) as usize], 127), "6a6c49d2cb75d5b6");
1996
1997        assert_eq!(block0_word(&passes[1][0], 0), "d3801200410f8c0d");
1998        assert_eq!(block_last_word(&passes[1][(m_prime - 1) as usize], 127), "2dbfff23f31b5883");
1999
2000        assert_eq!(block0_word(&passes[2][0], 0), "5f047b575c5ff4d2");
2001        assert_eq!(block_last_word(&passes[2][(m_prime - 1) as usize], 127), "c341b3ca45c10da5");
2002    }
2003
2004    #[test]
2005    fn test_rfc9106_argon2i_intermediate_blocks() {
2006        let pwd = vec![0x01u8; 32];
2007        let salt = vec![0x02u8; 16];
2008        let secret = vec![0x03u8; 8];
2009        let ad = vec![0x04u8; 12];
2010        let params = Params {
2011            iterations: 3,
2012            memory: 32,
2013            parallelism: 4,
2014        };
2015        let passes = argon2_core_with_passes(ARGON2I, &pwd, &salt, &secret, &ad, &params);
2016
2017        let p = params.parallelism;
2018        let q = (4 * p * (params.memory / (4 * p))) / p;
2019        let m_prime = p * q;
2020
2021        assert_eq!(block0_word(&passes[0][0], 0), "f8f9e84545db08f6");
2022        assert_eq!(block_last_word(&passes[0][(m_prime - 1) as usize], 127), "c570f2ab2a86cf00");
2023
2024        assert_eq!(block0_word(&passes[1][0], 0), "b2e4ddfcf76dc85a");
2025        assert_eq!(block_last_word(&passes[1][(m_prime - 1) as usize], 127), "421b3c6e9555b79d");
2026
2027        assert_eq!(block0_word(&passes[2][0], 0), "af2a8bd8482c2f11");
2028        assert_eq!(block_last_word(&passes[2][(m_prime - 1) as usize], 127), "71e436f035f30ed0");
2029    }
2030
2031    // ================================================================
2032    // RFC 9106 H_0 pre-hashing digest tests for all types
2033    // ================================================================
2034
2035    #[test]
2036    fn test_h0_argon2d() {
2037        let pwd = vec![0x01u8; 32];
2038        let salt = vec![0x02u8; 16];
2039        let secret = vec![0x03u8; 8];
2040        let ad = vec![0x04u8; 12];
2041        let h0 = compute_h0(ARGON2D, &pwd, &salt, &secret, &ad, 4, 32, 32, 3);
2042        let expected = "b8819791a0359660bb7709c85fa48f04d5d82c05c5f215ccdb885491717cf757082c28b951be381410b5fc2eb7274033b9fdc7ae672bcaac5d179097a4af3109";
2043        assert_eq!(hex::encode(h0), expected);
2044    }
2045
2046    #[test]
2047    fn test_h0_argon2i() {
2048        let pwd = vec![0x01u8; 32];
2049        let salt = vec![0x02u8; 16];
2050        let secret = vec![0x03u8; 8];
2051        let ad = vec![0x04u8; 12];
2052        let h0 = compute_h0(ARGON2I, &pwd, &salt, &secret, &ad, 4, 32, 32, 3);
2053        let expected = "c46065815276a0b3e731731c902f1fd80cf776907fbb7b6a5ca72e7b56011feeca446c86dd75b9469a5e6879dec4b72d0863fb939b982e5f397cc7d164fddaa9";
2054        assert_eq!(hex::encode(h0), expected);
2055    }
2056
2057    // ================================================================
2058    // Additional test vectors from various sources
2059    // ================================================================
2060
2061    #[test]
2062    fn test_argon2id_empty_secret_and_ad() {
2063        let params = Params {
2064            iterations: 1,
2065            memory: 64,
2066            parallelism: 1,
2067        };
2068        let result = derive_key_vec(32, b"password", b"saltsaltsaltsalt", &[], &[], &params);
2069        assert_eq!(result.len(), 32);
2070        let result2 = derive_key_vec(32, b"password", b"saltsaltsaltsalt", &[], &[], &params);
2071        assert_eq!(result, result2);
2072    }
2073
2074    #[test]
2075    fn test_argon2id_with_secret() {
2076        let p = Params {
2077            iterations: 1,
2078            memory: 64,
2079            parallelism: 1,
2080        };
2081        let without_secret = derive_key_vec(32, b"password", b"saltsaltsaltsalt", &[], &[], &p);
2082        let with_secret = derive_key_vec(32, b"password", b"saltsaltsaltsalt", b"secret", &[], &p);
2083        assert_ne!(without_secret, with_secret);
2084    }
2085
2086    #[test]
2087    fn test_argon2id_with_ad() {
2088        let p = Params {
2089            iterations: 1,
2090            memory: 64,
2091            parallelism: 1,
2092        };
2093        let without_ad = derive_key_vec(32, b"password", b"saltsaltsaltsalt", &[], &[], &p);
2094        let with_ad = derive_key_vec(32, b"password", b"saltsaltsaltsalt", &[], b"associated data", &p);
2095        assert_ne!(without_ad, with_ad);
2096    }
2097
2098    #[test]
2099    fn test_argon2id_tag_length_4() {
2100        let result = derive_key_vec(
2101            4,
2102            b"password",
2103            b"saltsaltsaltsalt",
2104            &[],
2105            &[],
2106            &Params {
2107                iterations: 1,
2108                memory: 64,
2109                parallelism: 1,
2110            },
2111        );
2112        assert_eq!(result.len(), 4);
2113    }
2114
2115    #[test]
2116    fn test_argon2id_tag_length_128() {
2117        let result = derive_key_vec(
2118            128,
2119            b"password",
2120            b"saltsaltsaltsalt",
2121            &[],
2122            &[],
2123            &Params {
2124                iterations: 1,
2125                memory: 64,
2126                parallelism: 1,
2127            },
2128        );
2129        assert_eq!(result.len(), 128);
2130    }
2131
2132    #[test]
2133    fn test_argon2id_tag_length_256() {
2134        let result = derive_key_vec(
2135            256,
2136            b"password",
2137            b"saltsaltsaltsalt",
2138            &[],
2139            &[],
2140            &Params {
2141                iterations: 1,
2142                memory: 64,
2143                parallelism: 1,
2144            },
2145        );
2146        assert_eq!(result.len(), 256);
2147    }
2148
2149    #[test]
2150    fn test_argon2id_long_tag_consistency() {
2151        let p = Params {
2152            iterations: 1,
2153            memory: 64,
2154            parallelism: 1,
2155        };
2156        let r1 = derive_key_vec(100, b"password", b"saltsaltsaltsalt", &[], &[], &p);
2157        let r2 = derive_key_vec(100, b"password", b"saltsaltsaltsalt", &[], &[], &p);
2158        assert_eq!(r1, r2);
2159        assert_eq!(r1.len(), 100);
2160    }
2161
2162    #[test]
2163    fn test_argon2i_long_tag_consistency() {
2164        let params = Params {
2165            iterations: 1,
2166            memory: 64,
2167            parallelism: 1,
2168        };
2169        let mut r1 = [0u8; 100];
2170        let mut r2 = [0u8; 100];
2171        argon2_core(ARGON2I, b"password", b"saltsaltsaltsalt", &[], &[], &params, &mut r1).unwrap();
2172        argon2_core(ARGON2I, b"password", b"saltsaltsaltsalt", &[], &[], &params, &mut r2).unwrap();
2173        assert_eq!(r1, r2);
2174    }
2175
2176    #[test]
2177    fn test_argon2d_long_tag_consistency() {
2178        let params = Params {
2179            iterations: 1,
2180            memory: 64,
2181            parallelism: 1,
2182        };
2183        let mut r1 = [0u8; 100];
2184        let mut r2 = [0u8; 100];
2185        argon2_core(ARGON2D, b"password", b"saltsaltsaltsalt", &[], &[], &params, &mut r1).unwrap();
2186        argon2_core(ARGON2D, b"password", b"saltsaltsaltsalt", &[], &[], &params, &mut r2).unwrap();
2187        assert_eq!(r1, r2);
2188    }
2189
2190    #[test]
2191    fn test_argon2id_single_pass() {
2192        let result = derive_key_vec(
2193            32,
2194            b"password",
2195            b"saltsalt",
2196            &[],
2197            &[],
2198            &Params {
2199                iterations: 1,
2200                memory: 32,
2201                parallelism: 1,
2202            },
2203        );
2204        assert_eq!(result.len(), 32);
2205    }
2206
2207    #[test]
2208    fn test_argon2id_high_parallelism() {
2209        let result = derive_key_vec(
2210            32,
2211            b"password",
2212            b"saltsaltsaltsalt",
2213            &[],
2214            &[],
2215            &Params {
2216                iterations: 1,
2217                memory: 64,
2218                parallelism: 8,
2219            },
2220        );
2221        assert_eq!(result.len(), 32);
2222    }
2223
2224    #[test]
2225    fn test_argon2d_rfc_h0() {
2226        let pwd = vec![0x01u8; 32];
2227        let salt = vec![0x02u8; 16];
2228        let secret = vec![0x03u8; 8];
2229        let ad = vec![0x04u8; 12];
2230        let h0 = compute_h0(ARGON2D, &pwd, &salt, &secret, &ad, 4, 32, 32, 3);
2231        assert_eq!(h0[0], 0xb8);
2232        assert_eq!(h0[1], 0x81);
2233        assert_eq!(h0[63], 0x09);
2234    }
2235
2236    #[test]
2237    fn test_variable_length_hash_exact_64() {
2238        let input = b"test";
2239        let result = variable_length_hash_vec(input, 64);
2240        assert_eq!(result.len(), 64);
2241    }
2242
2243    #[test]
2244    fn test_variable_length_hash_65_bytes() {
2245        let input = b"test";
2246        let result = variable_length_hash_vec(input, 65);
2247        assert_eq!(result.len(), 65);
2248        let result2 = variable_length_hash_vec(input, 65);
2249        assert_eq!(result, result2);
2250    }
2251
2252    #[test]
2253    fn test_variable_length_hash_deterministic() {
2254        for len in [4, 16, 32, 48, 64, 65, 96, 128, 256, 512, 1024] {
2255            let r1 = variable_length_hash_vec(b"determinism test", len);
2256            let r2 = variable_length_hash_vec(b"determinism test", len);
2257            assert_eq!(r1, r2, "variable_length_hash not deterministic for len={}", len);
2258            assert_eq!(r1.len(), len);
2259        }
2260    }
2261
2262    #[test]
2263    fn test_compress_deterministic() {
2264        let a = Block::from_bytes(&[0xAA; BLOCK_SIZE]);
2265        let b = Block::from_bytes(&[0xBB; BLOCK_SIZE]);
2266        let c1 = compress(&a, &b);
2267        let c2 = compress(&a, &b);
2268        assert_eq!(c1.v, c2.v);
2269    }
2270
2271    #[test]
2272    fn test_compress_xor_symmetry() {
2273        let a = Block::from_bytes(&[0x11; BLOCK_SIZE]);
2274        let b = Block::from_bytes(&[0x22; BLOCK_SIZE]);
2275        let c_ab = compress(&a, &b);
2276        let c_ba = compress(&b, &a);
2277        assert_eq!(c_ab.v, c_ba.v, "G(X,Y) should equal G(Y,X) since R = X XOR Y is symmetric");
2278    }
2279
2280    #[test]
2281    fn test_block_from_bytes_roundtrip() {
2282        let original = [0x42u8; BLOCK_SIZE];
2283        let block = Block::from_bytes(&original);
2284        let recovered = block.to_bytes();
2285        assert_eq!(original, recovered);
2286    }
2287
2288    #[test]
2289    fn test_argon2id_different_iterations() {
2290        let p1 = Params {
2291            iterations: 1,
2292            memory: 64,
2293            parallelism: 1,
2294        };
2295        let p2 = Params {
2296            iterations: 2,
2297            memory: 64,
2298            parallelism: 1,
2299        };
2300        let r1 = derive_key_vec(32, b"password", b"saltsaltsaltsalt", &[], &[], &p1);
2301        let r2 = derive_key_vec(32, b"password", b"saltsaltsaltsalt", &[], &[], &p2);
2302        assert_ne!(r1, r2);
2303    }
2304
2305    #[test]
2306    fn test_argon2id_different_memory() {
2307        let p1 = Params {
2308            iterations: 1,
2309            memory: 64,
2310            parallelism: 1,
2311        };
2312        let p2 = Params {
2313            iterations: 1,
2314            memory: 128,
2315            parallelism: 1,
2316        };
2317        let r1 = derive_key_vec(32, b"password", b"saltsaltsaltsalt", &[], &[], &p1);
2318        let r2 = derive_key_vec(32, b"password", b"saltsaltsaltsalt", &[], &[], &p2);
2319        assert_ne!(r1, r2);
2320    }
2321
2322    #[test]
2323    fn test_argon2id_different_parallelisms() {
2324        let p1 = Params {
2325            iterations: 1,
2326            memory: 64,
2327            parallelism: 1,
2328        };
2329        let p2 = Params {
2330            iterations: 1,
2331            memory: 64,
2332            parallelism: 2,
2333        };
2334        let r1 = derive_key_vec(32, b"password", b"saltsaltsaltsalt", &[], &[], &p1);
2335        let r2 = derive_key_vec(32, b"password", b"saltsaltsaltsalt", &[], &[], &p2);
2336        assert_ne!(r1, r2);
2337    }
2338
2339    #[test]
2340    fn test_argon2i_rfc9106_tag() {
2341        let pwd = vec![0x01u8; 32];
2342        let salt = vec![0x02u8; 16];
2343        let secret = vec![0x03u8; 8];
2344        let ad = vec![0x04u8; 12];
2345        let expected = hex::decode("c814d9d1dc7f37aa13f0d77f2494bda1c8de6b016dd388d29952a4c4672b6ce8").unwrap();
2346        let result = derive_key_typed(ARGON2I, &pwd, &salt, &secret, &ad, 3, 32, 4, 32);
2347        assert_eq!(result, expected);
2348    }
2349
2350    #[test]
2351    fn test_argon2d_rfc9106_tag() {
2352        let pwd = vec![0x01u8; 32];
2353        let salt = vec![0x02u8; 16];
2354        let secret = vec![0x03u8; 8];
2355        let ad = vec![0x04u8; 12];
2356        let expected = hex::decode("512b391b6f1162975371d30919734294f868e3be3984f3c1a13a4db9fabe4acb").unwrap();
2357        let result = derive_key_typed(ARGON2D, &pwd, &salt, &secret, &ad, 3, 32, 4, 32);
2358        assert_eq!(result, expected);
2359    }
2360
2361    #[test]
2362    fn test_phc_verify_known() {
2363        let password = b"password";
2364        let salt = b"randomsalt123456";
2365        let params = Params {
2366            iterations: 1,
2367            memory: 64,
2368            parallelism: 1,
2369        };
2370        let encoded = hash_password(password, salt, &params).unwrap();
2371        assert!(verify_password(password, &encoded, None).is_ok());
2372        assert_eq!(verify_password(b"wrong", &encoded, None), Err(Argon2Error::VerifyMismatch));
2373    }
2374
2375    #[test]
2376    fn test_decode_phc_roundtrip_all_types() {
2377        for tag_len in [4, 16, 32, 64] {
2378            let params = Params {
2379                iterations: 1,
2380                memory: 64,
2381                parallelism: 1,
2382            };
2383            let salt = b"testsalt12345678";
2384            let tag = vec![0xAB; tag_len as usize];
2385            let encoded = encode_phc(&params, salt, &tag);
2386            let (dp, ds, dt) = decode_phc(&encoded).unwrap();
2387            assert_eq!(dp.memory, 64);
2388            assert_eq!(dp.iterations, 1);
2389            assert_eq!(dp.parallelism, 1);
2390            assert_eq!(ds, salt);
2391            assert_eq!(dt, tag);
2392        }
2393    }
2394
2395    #[test]
2396    fn test_index_alpha_pass0_slice0() {
2397        let result = index_alpha(0, 0, 4, 2, 2, 8, true, 0xFFFFFFFF);
2398        assert!(result < 8);
2399    }
2400
2401    #[test]
2402    fn test_index_alpha_reference_area_size_zero() {
2403        let result = index_alpha(0, 0, 4, 2, 0, 8, true, 0xFFFFFFFF);
2404        assert_eq!(result, 0);
2405    }
2406
2407    #[test]
2408    fn test_permutation_p_changes_values() {
2409        let mut v = [0u64; 16];
2410        v[0] = 1;
2411        v[1] = 2;
2412        v[2] = 3;
2413        v[3] = 4;
2414        permutation_p(&mut v);
2415        assert_ne!(v[0], 1);
2416        assert_ne!(v[1], 2);
2417        assert_ne!(v[2], 3);
2418        assert_ne!(v[3], 4);
2419    }
2420
2421    #[test]
2422    fn test_permutation_p_deterministic() {
2423        let mut v1 = [0u64; 16];
2424        for (i, word) in v1.iter_mut().enumerate() {
2425            *word = i as u64;
2426        }
2427        let mut v2 = v1;
2428        permutation_p(&mut v1);
2429        permutation_p(&mut v2);
2430        assert_eq!(v1, v2);
2431    }
2432}