Skip to main content

crypto/
argon2.rs

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