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