1use alloc::vec;
40use ic_core::{ensure, Result, Zeroize};
41use ic_hash::{blake2b_long, Blake2b};
42
43const BLOCK_LEN: usize = 1024;
45
46const BLOCK_WORDS: usize = BLOCK_LEN / 8;
48
49const SLICES: usize = 4;
51
52const VERSION: u32 = 0x13;
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57pub enum Variant {
58 Argon2d,
60 Argon2i,
62 Argon2id,
64}
65
66impl Variant {
67 const fn type_id(self) -> u32 {
69 match self {
70 Self::Argon2d => 0,
71 Self::Argon2i => 1,
72 Self::Argon2id => 2,
73 }
74 }
75
76 pub const fn id(self) -> &'static str {
78 match self {
79 Self::Argon2d => "argon2d",
80 Self::Argon2i => "argon2i",
81 Self::Argon2id => "argon2id",
82 }
83 }
84
85 fn independent(self, pass: u32, slice: usize) -> bool {
87 match self {
88 Self::Argon2d => false,
89 Self::Argon2i => true,
90 Self::Argon2id => pass == 0 && slice < 2,
92 }
93 }
94}
95
96#[derive(Debug, Clone, Copy)]
98pub struct Argon2Params {
99 pub memory_kib: u32,
101 pub passes: u32,
103 pub lanes: u32,
105}
106
107impl Argon2Params {
108 pub const RECOMMENDED: Argon2Params = Argon2Params {
113 memory_kib: 2 * 1024 * 1024,
114 passes: 1,
115 lanes: 4,
116 };
117
118 pub const INTERACTIVE: Argon2Params = Argon2Params {
123 memory_kib: 64 * 1024,
124 passes: 3,
125 lanes: 4,
126 };
127
128 pub fn validate(&self) -> Result<()> {
130 ensure!(
131 self.lanes >= 1 && self.lanes <= 0x00FF_FFFF,
132 InvalidParameter,
133 "argon2 lanes must be 1..=2^24-1"
134 );
135 ensure!(
136 self.passes >= 1,
137 InvalidParameter,
138 "argon2 passes must be at least 1"
139 );
140 ensure!(
141 self.memory_kib >= 8 * self.lanes,
142 InvalidParameter,
143 "argon2 memory must be at least 8 KiB per lane"
144 );
145 Ok(())
146 }
147}
148
149#[derive(Clone, Copy)]
151struct Block([u64; BLOCK_WORDS]);
152
153impl Block {
154 const ZERO: Block = Block([0u64; BLOCK_WORDS]);
155
156 fn from_bytes(bytes: &[u8; BLOCK_LEN]) -> Block {
157 let mut b = [0u64; BLOCK_WORDS];
158 for (i, word) in b.iter_mut().enumerate() {
159 let mut w = [0u8; 8];
160 w.copy_from_slice(&bytes[i * 8..i * 8 + 8]);
161 *word = u64::from_le_bytes(w);
162 }
163 Block(b)
164 }
165
166 fn to_bytes(self) -> [u8; BLOCK_LEN] {
167 let mut out = [0u8; BLOCK_LEN];
168 for (chunk, word) in out.chunks_exact_mut(8).zip(self.0.iter()) {
169 chunk.copy_from_slice(&word.to_le_bytes());
170 }
171 out
172 }
173
174 fn xor(&self, other: &Block) -> Block {
175 let mut out = [0u64; BLOCK_WORDS];
176 for ((slot, a), b) in out.iter_mut().zip(self.0.iter()).zip(other.0.iter()) {
177 *slot = a ^ b;
178 }
179 Block(out)
180 }
181}
182
183#[inline(always)]
190fn gb(v: &mut [u64; 16], a: usize, b: usize, c: usize, d: usize) {
191 #[inline(always)]
192 fn mix(x: u64, y: u64) -> u64 {
193 x.wrapping_add(y).wrapping_add(
194 2u64.wrapping_mul(x & 0xFFFF_FFFF)
195 .wrapping_mul(y & 0xFFFF_FFFF),
196 )
197 }
198
199 v[a] = mix(v[a], v[b]);
200 v[d] = (v[d] ^ v[a]).rotate_right(32);
201 v[c] = mix(v[c], v[d]);
202 v[b] = (v[b] ^ v[c]).rotate_right(24);
203 v[a] = mix(v[a], v[b]);
204 v[d] = (v[d] ^ v[a]).rotate_right(16);
205 v[c] = mix(v[c], v[d]);
206 v[b] = (v[b] ^ v[c]).rotate_right(63);
207}
208
209#[inline]
211fn permute(v: &mut [u64; 16]) {
212 gb(v, 0, 4, 8, 12);
213 gb(v, 1, 5, 9, 13);
214 gb(v, 2, 6, 10, 14);
215 gb(v, 3, 7, 11, 15);
216 gb(v, 0, 5, 10, 15);
217 gb(v, 1, 6, 11, 12);
218 gb(v, 2, 7, 8, 13);
219 gb(v, 3, 4, 9, 14);
220}
221
222fn compress(x: &Block, y: &Block) -> Block {
228 let r = x.xor(y);
229 let mut q = r;
230
231 for row in 0..8 {
233 let mut v = [0u64; 16];
234 v.copy_from_slice(&q.0[row * 16..row * 16 + 16]);
235 permute(&mut v);
236 q.0[row * 16..row * 16 + 16].copy_from_slice(&v);
237 }
238
239 for col in 0..8 {
241 let mut v = [0u64; 16];
242 for i in 0..8 {
243 v[i * 2] = q.0[i * 16 + col * 2];
244 v[i * 2 + 1] = q.0[i * 16 + col * 2 + 1];
245 }
246 permute(&mut v);
247 for i in 0..8 {
248 q.0[i * 16 + col * 2] = v[i * 2];
249 q.0[i * 16 + col * 2 + 1] = v[i * 2 + 1];
250 }
251 }
252
253 q.xor(&r)
254}
255
256#[allow(clippy::too_many_arguments)]
258fn reference_index(
259 j1: u32,
260 j2: u32,
261 pass: u32,
262 lane: u32,
263 slice: usize,
264 index: usize,
265 lanes: u32,
266 lane_len: usize,
267 segment_len: usize,
268) -> usize {
269 let ref_lane = if pass == 0 && slice == 0 {
272 lane
273 } else {
274 j2 % lanes
275 };
276
277 let same_lane = ref_lane == lane;
279 let mut reference_area = if pass == 0 {
280 if slice == 0 {
281 index - 1
282 } else if same_lane {
283 slice * segment_len + index - 1
284 } else {
285 slice * segment_len - usize::from(index == 0)
286 }
287 } else if same_lane {
288 lane_len - segment_len + index - 1
289 } else {
290 lane_len - segment_len - usize::from(index == 0)
291 };
292 if reference_area == usize::MAX {
293 reference_area = 0;
294 }
295
296 let x = ((j1 as u64) * (j1 as u64)) >> 32;
299 let y = ((reference_area as u64) * x) >> 32;
300 let z = (reference_area as u64) - 1 - y;
301
302 let start = if pass == 0 || slice == SLICES - 1 {
303 0
304 } else {
305 (slice + 1) * segment_len
306 };
307 let position = (start as u64 + z) % (lane_len as u64);
308 (ref_lane as usize) * lane_len + position as usize
309}
310
311pub fn argon2(
316 variant: Variant,
317 params: &Argon2Params,
318 password: &[u8],
319 salt: &[u8],
320 out: &mut [u8],
321) -> Result<()> {
322 argon2_full(variant, params, password, salt, &[], &[], out)
323}
324
325pub fn argon2_full(
331 variant: Variant,
332 params: &Argon2Params,
333 password: &[u8],
334 salt: &[u8],
335 secret: &[u8],
336 associated_data: &[u8],
337 out: &mut [u8],
338) -> Result<()> {
339 params.validate()?;
340 ensure!(
341 out.len() >= 4,
342 InvalidLength,
343 "argon2 tag must be >= 4 bytes"
344 );
345 ensure!(
346 salt.len() >= 8,
347 InvalidParameter,
348 "argon2 salt must be >= 8 bytes"
349 );
350
351 let lanes = params.lanes;
352 let passes = params.passes;
353
354 let blocks = core::cmp::max(params.memory_kib, 8 * lanes);
356 let blocks = (blocks / (SLICES as u32 * lanes)) * (SLICES as u32 * lanes);
357 let lane_len = (blocks / lanes) as usize;
358 let segment_len = lane_len / SLICES;
359 let total = blocks as usize;
360
361 let mut h0 = [0u8; 72];
367 {
368 let mut hasher = Blake2b::new(64)?;
369 let le = |v: u32| v.to_le_bytes();
370 hasher.update(&le(lanes));
371 hasher.update(&le(out.len() as u32));
372 hasher.update(&le(params.memory_kib));
373 hasher.update(&le(passes));
374 hasher.update(&le(VERSION));
375 hasher.update(&le(variant.type_id()));
376 hasher.update(&le(password.len() as u32));
377 hasher.update(password);
378 hasher.update(&le(salt.len() as u32));
379 hasher.update(salt);
380 hasher.update(&le(secret.len() as u32));
381 hasher.update(secret);
382 hasher.update(&le(associated_data.len() as u32));
383 hasher.update(associated_data);
384
385 let mut seed = [0u8; 64];
386 hasher.finalize_into(&mut seed)?;
387 h0[..64].copy_from_slice(&seed);
388 seed.zeroize();
389 }
390
391 let mut memory = vec![Block::ZERO; total];
392
393 for lane in 0..lanes {
395 for index in 0..2u32 {
396 h0[64..68].copy_from_slice(&index.to_le_bytes());
397 h0[68..72].copy_from_slice(&lane.to_le_bytes());
398 let mut block = [0u8; BLOCK_LEN];
399 blake2b_long(&[&h0], &mut block)?;
400 memory[lane as usize * lane_len + index as usize] = Block::from_bytes(&block);
401 block.zeroize();
402 }
403 }
404
405 for pass in 0..passes {
407 for slice in 0..SLICES {
408 for lane in 0..lanes {
409 let mut addresses = [0u64; BLOCK_WORDS];
410 let independent = variant.independent(pass, slice);
411 let mut address_counter = 0u64;
412
413 let start = if pass == 0 && slice == 0 { 2 } else { 0 };
414 for index in start..segment_len {
415 let position = slice * segment_len + index;
416 let current = lane as usize * lane_len + position;
417 let previous = if position == 0 {
418 lane as usize * lane_len + lane_len - 1
419 } else {
420 current - 1
421 };
422
423 let (j1, j2) = if independent {
424 if index % BLOCK_WORDS == 0 || address_counter == 0 {
426 address_counter += 1;
427 let mut input = Block::ZERO;
428 input.0[0] = pass as u64;
429 input.0[1] = lane as u64;
430 input.0[2] = slice as u64;
431 input.0[3] = total as u64;
432 input.0[4] = passes as u64;
433 input.0[5] = variant.type_id() as u64;
434 input.0[6] = address_counter;
435 let zero = Block::ZERO;
436 let tmp = compress(&zero, &input);
437 let block = compress(&zero, &tmp);
438 addresses.copy_from_slice(&block.0);
439 }
440 let word = addresses[index % BLOCK_WORDS];
441 (word as u32, (word >> 32) as u32)
442 } else {
443 let word = memory[previous].0[0];
444 (word as u32, (word >> 32) as u32)
445 };
446
447 let ref_index = reference_index(
448 j1,
449 j2,
450 pass,
451 lane,
452 slice,
453 index,
454 lanes,
455 lane_len,
456 segment_len,
457 );
458
459 let mixed = compress(&memory[previous], &memory[ref_index]);
460 memory[current] = if pass == 0 {
461 mixed
462 } else {
463 mixed.xor(&memory[current])
466 };
467 }
468 }
469 }
470 }
471
472 let mut final_block = memory[lane_len - 1];
474 for lane in 1..lanes as usize {
475 final_block = final_block.xor(&memory[lane * lane_len + lane_len - 1]);
476 }
477
478 let bytes = final_block.to_bytes();
479 blake2b_long(&[&bytes], out)?;
480
481 for block in memory.iter_mut() {
483 block.0.zeroize();
484 }
485 h0.zeroize();
486 Ok(())
487}
488
489#[cfg(test)]
490mod tests {
491 use super::*;
492 use ic_core::codec::hex;
493
494 fn rfc_inputs() -> ([u8; 32], [u8; 16], [u8; 8], [u8; 12], Argon2Params) {
496 (
497 [0x01u8; 32],
498 [0x02u8; 16],
499 [0x03u8; 8],
500 [0x04u8; 12],
501 Argon2Params {
502 memory_kib: 32,
503 passes: 3,
504 lanes: 4,
505 },
506 )
507 }
508
509 fn rfc_tag(variant: Variant) -> String {
510 let (password, salt, secret, ad, params) = rfc_inputs();
511 let mut out = [0u8; 32];
512 argon2_full(variant, ¶ms, &password, &salt, &secret, &ad, &mut out).unwrap();
513 hex(&out)
514 }
515
516 #[test]
518 fn rfc9106_argon2id_vector() {
519 assert_eq!(
520 rfc_tag(Variant::Argon2id),
521 "0d640df58d78766c08c037a34a8b53c9d01ef0452d75b65eb52520e96b01e659"
522 );
523 }
524
525 #[test]
527 fn rfc9106_argon2d_vector() {
528 assert_eq!(
529 rfc_tag(Variant::Argon2d),
530 "512b391b6f1162975371d30919734294f868e3be3984f3c1a13a4db9fabe4acb"
531 );
532 }
533
534 #[test]
536 fn rfc9106_argon2i_vector() {
537 assert_eq!(
538 rfc_tag(Variant::Argon2i),
539 "c814d9d1dc7f37aa13f0d77f2494bda1c8de6b016dd388d29952a4c4672b6ce8"
540 );
541 }
542
543 #[test]
544 fn is_deterministic() {
545 let params = Argon2Params {
546 memory_kib: 32,
547 passes: 2,
548 lanes: 1,
549 };
550 let mut a = [0u8; 32];
551 let mut b = [0u8; 32];
552 argon2(Variant::Argon2id, ¶ms, b"pw", b"salt-8-b", &mut a).unwrap();
553 argon2(Variant::Argon2id, ¶ms, b"pw", b"salt-8-b", &mut b).unwrap();
554 assert_eq!(a, b);
555 }
556
557 #[test]
558 fn every_input_changes_the_tag() {
559 let params = Argon2Params {
560 memory_kib: 32,
561 passes: 1,
562 lanes: 1,
563 };
564 let base = {
565 let mut o = [0u8; 32];
566 argon2(Variant::Argon2id, ¶ms, b"pw", b"salt-8-b", &mut o).unwrap();
567 o
568 };
569
570 let mut changed = [0u8; 32];
571 argon2(Variant::Argon2id, ¶ms, b"px", b"salt-8-b", &mut changed).unwrap();
572 assert_ne!(base, changed, "password");
573
574 argon2(Variant::Argon2id, ¶ms, b"pw", b"salt-8-c", &mut changed).unwrap();
575 assert_ne!(base, changed, "salt");
576
577 let more = Argon2Params {
578 passes: 2,
579 ..params
580 };
581 argon2(Variant::Argon2id, &more, b"pw", b"salt-8-b", &mut changed).unwrap();
582 assert_ne!(base, changed, "passes");
583
584 let bigger = Argon2Params {
585 memory_kib: 64,
586 ..params
587 };
588 argon2(Variant::Argon2id, &bigger, b"pw", b"salt-8-b", &mut changed).unwrap();
589 assert_ne!(base, changed, "memory");
590
591 argon2(Variant::Argon2d, ¶ms, b"pw", b"salt-8-b", &mut changed).unwrap();
592 assert_ne!(base, changed, "variant");
593 }
594
595 #[test]
598 fn tag_length_is_bound_in() {
599 let params = Argon2Params {
600 memory_kib: 32,
601 passes: 1,
602 lanes: 1,
603 };
604 let mut short = [0u8; 16];
605 let mut long = [0u8; 64];
606 argon2(Variant::Argon2id, ¶ms, b"pw", b"salt-8-b", &mut short).unwrap();
607 argon2(Variant::Argon2id, ¶ms, b"pw", b"salt-8-b", &mut long).unwrap();
608 assert_ne!(&long[..16], &short[..]);
609 }
610
611 #[test]
612 fn parallelism_is_honoured() {
613 let one = Argon2Params {
614 memory_kib: 64,
615 passes: 1,
616 lanes: 1,
617 };
618 let four = Argon2Params { lanes: 4, ..one };
619 let mut a = [0u8; 32];
620 let mut b = [0u8; 32];
621 argon2(Variant::Argon2id, &one, b"pw", b"salt-8-b", &mut a).unwrap();
622 argon2(Variant::Argon2id, &four, b"pw", b"salt-8-b", &mut b).unwrap();
623 assert_ne!(a, b);
624 }
625
626 #[test]
627 fn rejects_invalid_parameters() {
628 let ok = Argon2Params {
629 memory_kib: 32,
630 passes: 1,
631 lanes: 1,
632 };
633 let mut out = [0u8; 32];
634
635 assert!(
636 argon2(Variant::Argon2id, &ok, b"pw", b"short", &mut out).is_err(),
637 "salt"
638 );
639 assert!(
640 argon2(Variant::Argon2id, &ok, b"pw", b"salt-8-b", &mut [0u8; 3]).is_err(),
641 "tag too short"
642 );
643
644 let no_passes = Argon2Params { passes: 0, ..ok };
645 assert!(no_passes.validate().is_err());
646
647 let no_lanes = Argon2Params { lanes: 0, ..ok };
648 assert!(no_lanes.validate().is_err());
649
650 let too_little = Argon2Params {
651 memory_kib: 4,
652 lanes: 4,
653 passes: 1,
654 };
655 assert!(too_little.validate().is_err());
656 }
657
658 #[test]
659 fn recommended_parameters_validate() {
660 Argon2Params::RECOMMENDED.validate().unwrap();
661 Argon2Params::INTERACTIVE.validate().unwrap();
662 }
663
664 #[test]
665 fn variant_identifiers() {
666 assert_eq!(Variant::Argon2id.id(), "argon2id");
667 assert_eq!(Variant::Argon2id.type_id(), 2);
668 assert_eq!(Variant::Argon2i.type_id(), 1);
669 assert_eq!(Variant::Argon2d.type_id(), 0);
670 }
671
672 #[test]
675 fn argon2id_switches_indexing_halfway() {
676 assert!(Variant::Argon2id.independent(0, 0));
677 assert!(Variant::Argon2id.independent(0, 1));
678 assert!(!Variant::Argon2id.independent(0, 2));
679 assert!(!Variant::Argon2id.independent(1, 0));
680
681 assert!(Variant::Argon2i.independent(5, 3));
682 assert!(!Variant::Argon2d.independent(0, 0));
683 }
684}