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 ic_core::module::operational()?;
323 argon2_full(variant, params, password, salt, &[], &[], out)
324}
325
326pub fn argon2_full(
332 variant: Variant,
333 params: &Argon2Params,
334 password: &[u8],
335 salt: &[u8],
336 secret: &[u8],
337 associated_data: &[u8],
338 out: &mut [u8],
339) -> Result<()> {
340 ic_core::module::operational()?;
341 params.validate()?;
342 ensure!(
343 out.len() >= 4,
344 InvalidLength,
345 "argon2 tag must be >= 4 bytes"
346 );
347 ensure!(
348 salt.len() >= 8,
349 InvalidParameter,
350 "argon2 salt must be >= 8 bytes"
351 );
352
353 let lanes = params.lanes;
354 let passes = params.passes;
355
356 let blocks = core::cmp::max(params.memory_kib, 8 * lanes);
358 let blocks = (blocks / (SLICES as u32 * lanes)) * (SLICES as u32 * lanes);
359 let lane_len = (blocks / lanes) as usize;
360 let segment_len = lane_len / SLICES;
361 let total = blocks as usize;
362
363 let mut h0 = [0u8; 72];
369 {
370 let mut hasher = Blake2b::new(64)?;
371 let le = |v: u32| v.to_le_bytes();
372 hasher.update(&le(lanes));
373 hasher.update(&le(out.len() as u32));
374 hasher.update(&le(params.memory_kib));
375 hasher.update(&le(passes));
376 hasher.update(&le(VERSION));
377 hasher.update(&le(variant.type_id()));
378 hasher.update(&le(password.len() as u32));
379 hasher.update(password);
380 hasher.update(&le(salt.len() as u32));
381 hasher.update(salt);
382 hasher.update(&le(secret.len() as u32));
383 hasher.update(secret);
384 hasher.update(&le(associated_data.len() as u32));
385 hasher.update(associated_data);
386
387 let mut seed = [0u8; 64];
388 hasher.finalize_into(&mut seed)?;
389 h0[..64].copy_from_slice(&seed);
390 seed.zeroize();
391 }
392
393 let mut memory = vec![Block::ZERO; total];
394
395 for lane in 0..lanes {
397 for index in 0..2u32 {
398 h0[64..68].copy_from_slice(&index.to_le_bytes());
399 h0[68..72].copy_from_slice(&lane.to_le_bytes());
400 let mut block = [0u8; BLOCK_LEN];
401 blake2b_long(&[&h0], &mut block)?;
402 memory[lane as usize * lane_len + index as usize] = Block::from_bytes(&block);
403 block.zeroize();
404 }
405 }
406
407 for pass in 0..passes {
409 for slice in 0..SLICES {
410 for lane in 0..lanes {
411 let mut addresses = [0u64; BLOCK_WORDS];
412 let independent = variant.independent(pass, slice);
413 let mut address_counter = 0u64;
414
415 let start = if pass == 0 && slice == 0 { 2 } else { 0 };
416 for index in start..segment_len {
417 let position = slice * segment_len + index;
418 let current = lane as usize * lane_len + position;
419 let previous = if position == 0 {
420 lane as usize * lane_len + lane_len - 1
421 } else {
422 current - 1
423 };
424
425 let (j1, j2) = if independent {
426 if index % BLOCK_WORDS == 0 || address_counter == 0 {
428 address_counter += 1;
429 let mut input = Block::ZERO;
430 input.0[0] = pass as u64;
431 input.0[1] = lane as u64;
432 input.0[2] = slice as u64;
433 input.0[3] = total as u64;
434 input.0[4] = passes as u64;
435 input.0[5] = variant.type_id() as u64;
436 input.0[6] = address_counter;
437 let zero = Block::ZERO;
438 let tmp = compress(&zero, &input);
439 let block = compress(&zero, &tmp);
440 addresses.copy_from_slice(&block.0);
441 }
442 let word = addresses[index % BLOCK_WORDS];
443 (word as u32, (word >> 32) as u32)
444 } else {
445 let word = memory[previous].0[0];
446 (word as u32, (word >> 32) as u32)
447 };
448
449 let ref_index = reference_index(
450 j1,
451 j2,
452 pass,
453 lane,
454 slice,
455 index,
456 lanes,
457 lane_len,
458 segment_len,
459 );
460
461 let mixed = compress(&memory[previous], &memory[ref_index]);
462 memory[current] = if pass == 0 {
463 mixed
464 } else {
465 mixed.xor(&memory[current])
468 };
469 }
470 }
471 }
472 }
473
474 let mut final_block = memory[lane_len - 1];
476 for lane in 1..lanes as usize {
477 final_block = final_block.xor(&memory[lane * lane_len + lane_len - 1]);
478 }
479
480 let bytes = final_block.to_bytes();
481 blake2b_long(&[&bytes], out)?;
482
483 for block in memory.iter_mut() {
485 block.0.zeroize();
486 }
487 h0.zeroize();
488 Ok(())
489}
490
491#[cfg(test)]
492mod tests {
493 use super::*;
494 use ic_core::codec::hex;
495
496 fn rfc_inputs() -> ([u8; 32], [u8; 16], [u8; 8], [u8; 12], Argon2Params) {
498 (
499 [0x01u8; 32],
500 [0x02u8; 16],
501 [0x03u8; 8],
502 [0x04u8; 12],
503 Argon2Params {
504 memory_kib: 32,
505 passes: 3,
506 lanes: 4,
507 },
508 )
509 }
510
511 fn rfc_tag(variant: Variant) -> String {
512 let (password, salt, secret, ad, params) = rfc_inputs();
513 let mut out = [0u8; 32];
514 argon2_full(variant, ¶ms, &password, &salt, &secret, &ad, &mut out).unwrap();
515 hex(&out)
516 }
517
518 #[test]
520 fn rfc9106_argon2id_vector() {
521 assert_eq!(
522 rfc_tag(Variant::Argon2id),
523 "0d640df58d78766c08c037a34a8b53c9d01ef0452d75b65eb52520e96b01e659"
524 );
525 }
526
527 #[test]
529 fn rfc9106_argon2d_vector() {
530 assert_eq!(
531 rfc_tag(Variant::Argon2d),
532 "512b391b6f1162975371d30919734294f868e3be3984f3c1a13a4db9fabe4acb"
533 );
534 }
535
536 #[test]
538 fn rfc9106_argon2i_vector() {
539 assert_eq!(
540 rfc_tag(Variant::Argon2i),
541 "c814d9d1dc7f37aa13f0d77f2494bda1c8de6b016dd388d29952a4c4672b6ce8"
542 );
543 }
544
545 #[test]
546 fn is_deterministic() {
547 let params = Argon2Params {
548 memory_kib: 32,
549 passes: 2,
550 lanes: 1,
551 };
552 let mut a = [0u8; 32];
553 let mut b = [0u8; 32];
554 argon2(Variant::Argon2id, ¶ms, b"pw", b"salt-8-b", &mut a).unwrap();
555 argon2(Variant::Argon2id, ¶ms, b"pw", b"salt-8-b", &mut b).unwrap();
556 assert_eq!(a, b);
557 }
558
559 #[test]
560 fn every_input_changes_the_tag() {
561 let params = Argon2Params {
562 memory_kib: 32,
563 passes: 1,
564 lanes: 1,
565 };
566 let base = {
567 let mut o = [0u8; 32];
568 argon2(Variant::Argon2id, ¶ms, b"pw", b"salt-8-b", &mut o).unwrap();
569 o
570 };
571
572 let mut changed = [0u8; 32];
573 argon2(Variant::Argon2id, ¶ms, b"px", b"salt-8-b", &mut changed).unwrap();
574 assert_ne!(base, changed, "password");
575
576 argon2(Variant::Argon2id, ¶ms, b"pw", b"salt-8-c", &mut changed).unwrap();
577 assert_ne!(base, changed, "salt");
578
579 let more = Argon2Params {
580 passes: 2,
581 ..params
582 };
583 argon2(Variant::Argon2id, &more, b"pw", b"salt-8-b", &mut changed).unwrap();
584 assert_ne!(base, changed, "passes");
585
586 let bigger = Argon2Params {
587 memory_kib: 64,
588 ..params
589 };
590 argon2(Variant::Argon2id, &bigger, b"pw", b"salt-8-b", &mut changed).unwrap();
591 assert_ne!(base, changed, "memory");
592
593 argon2(Variant::Argon2d, ¶ms, b"pw", b"salt-8-b", &mut changed).unwrap();
594 assert_ne!(base, changed, "variant");
595 }
596
597 #[test]
600 fn tag_length_is_bound_in() {
601 let params = Argon2Params {
602 memory_kib: 32,
603 passes: 1,
604 lanes: 1,
605 };
606 let mut short = [0u8; 16];
607 let mut long = [0u8; 64];
608 argon2(Variant::Argon2id, ¶ms, b"pw", b"salt-8-b", &mut short).unwrap();
609 argon2(Variant::Argon2id, ¶ms, b"pw", b"salt-8-b", &mut long).unwrap();
610 assert_ne!(&long[..16], &short[..]);
611 }
612
613 #[test]
614 fn parallelism_is_honoured() {
615 let one = Argon2Params {
616 memory_kib: 64,
617 passes: 1,
618 lanes: 1,
619 };
620 let four = Argon2Params { lanes: 4, ..one };
621 let mut a = [0u8; 32];
622 let mut b = [0u8; 32];
623 argon2(Variant::Argon2id, &one, b"pw", b"salt-8-b", &mut a).unwrap();
624 argon2(Variant::Argon2id, &four, b"pw", b"salt-8-b", &mut b).unwrap();
625 assert_ne!(a, b);
626 }
627
628 #[test]
629 fn rejects_invalid_parameters() {
630 let ok = Argon2Params {
631 memory_kib: 32,
632 passes: 1,
633 lanes: 1,
634 };
635 let mut out = [0u8; 32];
636
637 assert!(
638 argon2(Variant::Argon2id, &ok, b"pw", b"short", &mut out).is_err(),
639 "salt"
640 );
641 assert!(
642 argon2(Variant::Argon2id, &ok, b"pw", b"salt-8-b", &mut [0u8; 3]).is_err(),
643 "tag too short"
644 );
645
646 let no_passes = Argon2Params { passes: 0, ..ok };
647 assert!(no_passes.validate().is_err());
648
649 let no_lanes = Argon2Params { lanes: 0, ..ok };
650 assert!(no_lanes.validate().is_err());
651
652 let too_little = Argon2Params {
653 memory_kib: 4,
654 lanes: 4,
655 passes: 1,
656 };
657 assert!(too_little.validate().is_err());
658 }
659
660 #[test]
661 fn recommended_parameters_validate() {
662 Argon2Params::RECOMMENDED.validate().unwrap();
663 Argon2Params::INTERACTIVE.validate().unwrap();
664 }
665
666 #[test]
667 fn variant_identifiers() {
668 assert_eq!(Variant::Argon2id.id(), "argon2id");
669 assert_eq!(Variant::Argon2id.type_id(), 2);
670 assert_eq!(Variant::Argon2i.type_id(), 1);
671 assert_eq!(Variant::Argon2d.type_id(), 0);
672 }
673
674 #[test]
677 fn argon2id_switches_indexing_halfway() {
678 assert!(Variant::Argon2id.independent(0, 0));
679 assert!(Variant::Argon2id.independent(0, 1));
680 assert!(!Variant::Argon2id.independent(0, 2));
681 assert!(!Variant::Argon2id.independent(1, 0));
682
683 assert!(Variant::Argon2i.independent(5, 3));
684 assert!(!Variant::Argon2d.independent(0, 0));
685 }
686}