Skip to main content

crypto/argon2/
mod.rs

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