1use alloc::vec::Vec;
13use cshake::digest::{ExtendableOutput, Update, XofReader};
14use oxicrypto_core::CryptoError;
15use shake::digest::{ExtendableOutput as ShakeExtend, Update as ShakeUpdate};
16
17pub struct Shake128Reader(shake::Shake128Reader);
22
23impl Shake128Reader {
24 pub fn read(&mut self, out: &mut [u8]) {
26 self.0.read(out);
27 }
28}
29
30pub struct Shake256Reader(shake::Shake256Reader);
33
34impl Shake256Reader {
35 pub fn read(&mut self, out: &mut [u8]) {
37 self.0.read(out);
38 }
39}
40
41pub fn shake128(msg: &[u8], out: &mut [u8]) {
43 let mut h = shake::Shake128::default();
44 ShakeUpdate::update(&mut h, msg);
45 let mut reader = ShakeExtend::finalize_xof(h);
46 reader.read(out);
47}
48
49pub fn shake256(msg: &[u8], out: &mut [u8]) {
51 let mut h = shake::Shake256::default();
52 ShakeUpdate::update(&mut h, msg);
53 let mut reader = ShakeExtend::finalize_xof(h);
54 reader.read(out);
55}
56
57pub fn shake128_start(msg: &[u8]) -> Shake128Reader {
62 let mut h = shake::Shake128::default();
63 ShakeUpdate::update(&mut h, msg);
64 Shake128Reader(ShakeExtend::finalize_xof(h))
65}
66
67pub fn shake256_start(msg: &[u8]) -> Shake256Reader {
69 let mut h = shake::Shake256::default();
70 ShakeUpdate::update(&mut h, msg);
71 Shake256Reader(ShakeExtend::finalize_xof(h))
72}
73
74pub fn cshake128(msg: &[u8], function_name: &[u8], customization: &[u8], out: &mut [u8]) {
81 let mut h = cshake::CShake128::new_with_function_name(function_name, customization);
82 Update::update(&mut h, msg);
83 let mut reader = ExtendableOutput::finalize_xof(h);
84 reader.read(out);
85}
86
87pub fn cshake256(msg: &[u8], function_name: &[u8], customization: &[u8], out: &mut [u8]) {
92 let mut h = cshake::CShake256::new_with_function_name(function_name, customization);
93 Update::update(&mut h, msg);
94 let mut reader = ExtendableOutput::finalize_xof(h);
95 reader.read(out);
96}
97
98pub(crate) fn left_encode(x: u64) -> Vec<u8> {
103 if x == 0 {
104 return alloc::vec![1u8, 0u8];
105 }
106 let be = x.to_be_bytes();
107 let leading_zeros = be.iter().take_while(|&&b| b == 0).count();
108 let n = 8 - leading_zeros; let mut out = Vec::with_capacity(1 + n);
110 out.push(n as u8);
111 out.extend_from_slice(&be[leading_zeros..]);
112 out
113}
114
115pub(crate) fn right_encode(x: u64) -> Vec<u8> {
118 if x == 0 {
119 return alloc::vec![0u8, 1u8];
120 }
121 let be = x.to_be_bytes();
122 let leading_zeros = be.iter().take_while(|&&b| b == 0).count();
123 let n = 8 - leading_zeros;
124 let mut out = Vec::with_capacity(n + 1);
125 out.extend_from_slice(&be[leading_zeros..]);
126 out.push(n as u8);
127 out
128}
129
130pub(crate) fn encode_string(s: &[u8]) -> Result<Vec<u8>, CryptoError> {
138 let bit_len = (s.len() as u64)
139 .checked_mul(8)
140 .ok_or(CryptoError::BadInput)?;
141 let mut out = left_encode(bit_len);
142 out.extend_from_slice(s);
143 Ok(out)
144}
145
146pub fn tuple_hash128(
161 inputs: &[&[u8]],
162 customization: &[u8],
163 out: &mut [u8],
164) -> Result<(), CryptoError> {
165 let mut h = cshake::CShake128::new_with_function_name(b"TupleHash", customization);
166
167 for &input in inputs {
169 let encoded = encode_string(input)?;
170 Update::update(&mut h, &encoded);
171 }
172
173 let out_bits = (out.len() as u64)
175 .checked_mul(8)
176 .ok_or(CryptoError::BadInput)?;
177 let renc = right_encode(out_bits);
178 Update::update(&mut h, &renc);
179
180 let mut reader = ExtendableOutput::finalize_xof(h);
181 reader.read(out);
182 Ok(())
183}
184
185pub fn tuple_hash256(
195 inputs: &[&[u8]],
196 customization: &[u8],
197 out: &mut [u8],
198) -> Result<(), CryptoError> {
199 let mut h = cshake::CShake256::new_with_function_name(b"TupleHash", customization);
200
201 for &input in inputs {
202 let encoded = encode_string(input)?;
203 Update::update(&mut h, &encoded);
204 }
205
206 let out_bits = (out.len() as u64)
207 .checked_mul(8)
208 .ok_or(CryptoError::BadInput)?;
209 let renc = right_encode(out_bits);
210 Update::update(&mut h, &renc);
211
212 let mut reader = ExtendableOutput::finalize_xof(h);
213 reader.read(out);
214 Ok(())
215}
216
217pub struct Blake2bKeyed {
224 key: Vec<u8>,
225}
226
227impl core::fmt::Debug for Blake2bKeyed {
228 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
229 write!(f, "Blake2bKeyed(***)")
230 }
231}
232
233impl Blake2bKeyed {
234 pub fn new(key: &[u8]) -> Result<Self, CryptoError> {
240 if key.is_empty() || key.len() > 64 {
241 return Err(CryptoError::InvalidKey);
242 }
243 Ok(Self { key: key.to_vec() })
244 }
245
246 pub fn hash(&self, msg: &[u8], out: &mut [u8]) -> Result<(), CryptoError> {
256 blake2b_keyed(&self.key, msg, out)
257 }
258}
259
260pub fn blake2b_keyed(key: &[u8], msg: &[u8], out: &mut [u8]) -> Result<(), CryptoError> {
269 use blake2::digest::{FixedOutput, KeyInit, Update as MacUpdate};
270
271 if key.is_empty() || key.len() > 64 {
272 return Err(CryptoError::InvalidKey);
273 }
274 if out.is_empty() || out.len() > 64 {
275 return Err(CryptoError::BadInput);
276 }
277
278 let mut mac =
280 blake2::Blake2bMac512::new_from_slice(key).map_err(|_| CryptoError::InvalidKey)?;
281 MacUpdate::update(&mut mac, msg);
282 let full = mac.finalize_fixed();
283 out.copy_from_slice(&full[..out.len()]);
284 Ok(())
285}
286
287#[cfg(feature = "std")]
297pub fn hash_file_sha256(path: &std::path::Path) -> Result<[u8; 32], CryptoError> {
298 use sha2::Digest;
299 use std::io::Read;
300
301 let file = std::fs::File::open(path).map_err(|_| CryptoError::Internal("cannot open file"))?;
302 let mut reader = std::io::BufReader::new(file);
303 let mut hasher = sha2::Sha256::new();
304 let mut buf = alloc::vec![0u8; 65536];
305
306 loop {
307 let n = reader
308 .read(&mut buf)
309 .map_err(|_| CryptoError::Internal("file read error"))?;
310 if n == 0 {
311 break;
312 }
313 Digest::update(&mut hasher, &buf[..n]);
314 }
315
316 let result = Digest::finalize(hasher);
317 let mut out = [0u8; 32];
318 out.copy_from_slice(&result);
319 Ok(out)
320}
321
322#[cfg(feature = "std")]
330pub fn hash_file_sha512(path: &std::path::Path) -> Result<[u8; 64], CryptoError> {
331 use sha2::Digest;
332 use std::io::Read;
333
334 let file = std::fs::File::open(path).map_err(|_| CryptoError::Internal("cannot open file"))?;
335 let mut reader = std::io::BufReader::new(file);
336 let mut hasher = sha2::Sha512::new();
337 let mut buf = alloc::vec![0u8; 65536];
338
339 loop {
340 let n = reader
341 .read(&mut buf)
342 .map_err(|_| CryptoError::Internal("file read error"))?;
343 if n == 0 {
344 break;
345 }
346 Digest::update(&mut hasher, &buf[..n]);
347 }
348
349 let result = Digest::finalize(hasher);
350 let mut out = [0u8; 64];
351 out.copy_from_slice(&result);
352 Ok(out)
353}
354
355#[cfg(feature = "std")]
363pub fn hash_file_blake3(path: &std::path::Path) -> Result<[u8; 32], CryptoError> {
364 use std::io::Read;
365
366 let file = std::fs::File::open(path).map_err(|_| CryptoError::Internal("cannot open file"))?;
367 let mut reader = std::io::BufReader::new(file);
368 let mut hasher = blake3::Hasher::new();
369 let mut buf = alloc::vec![0u8; 65536];
370
371 loop {
372 let n = reader
373 .read(&mut buf)
374 .map_err(|_| CryptoError::Internal("file read error"))?;
375 if n == 0 {
376 break;
377 }
378 hasher.update(&buf[..n]);
379 }
380
381 Ok(*hasher.finalize().as_bytes())
382}
383
384#[cfg(test)]
387mod tests {
388 use super::*;
389
390 #[test]
393 fn shake128_nonzero_output() {
394 let mut out = [0u8; 32];
395 shake128(b"abc", &mut out);
396 assert!(
397 out.iter().any(|&b| b != 0),
398 "SHAKE128 output must be non-zero"
399 );
400 }
401
402 #[test]
403 fn shake128_64_prefix_matches_32() {
404 let mut out32 = [0u8; 32];
405 let mut out64 = [0u8; 64];
406 shake128(b"abc", &mut out32);
407 shake128(b"abc", &mut out64);
408 assert_eq!(
409 out32,
410 out64[..32],
411 "64-byte SHAKE128 must be prefixed by 32-byte output"
412 );
413 }
414
415 #[test]
416 fn shake256_nonzero_output() {
417 let mut out = [0u8; 32];
418 shake256(b"abc", &mut out);
419 assert!(out.iter().any(|&b| b != 0));
420 }
421
422 #[test]
423 fn shake128_reader_matches_one_shot() {
424 let mut expected = [0u8; 48];
425 shake128(b"hello", &mut expected);
426
427 let mut reader = shake128_start(b"hello");
428 let mut actual = [0u8; 48];
429 reader.read(&mut actual);
430 assert_eq!(expected, actual);
431 }
432
433 #[test]
436 fn cshake128_empty_equals_shake128() {
437 let mut shake_out = [0u8; 32];
438 let mut cshake_out = [0u8; 32];
439 shake128(b"abc", &mut shake_out);
440 cshake128(b"abc", b"", b"", &mut cshake_out);
441 assert_eq!(
442 shake_out, cshake_out,
443 "cSHAKE128 with empty N and S must equal SHAKE128"
444 );
445 }
446
447 #[test]
448 fn cshake128_custom_differs_from_shake128() {
449 let mut shake_out = [0u8; 32];
450 let mut cshake_out = [0u8; 32];
451 shake128(b"abc", &mut shake_out);
452 cshake128(b"abc", b"Email Signature", b"", &mut cshake_out);
453 assert_ne!(
454 shake_out, cshake_out,
455 "cSHAKE128 with non-empty N must differ from SHAKE128"
456 );
457 }
458
459 #[test]
460 fn cshake256_empty_equals_shake256() {
461 let mut shake_out = [0u8; 64];
462 let mut cshake_out = [0u8; 64];
463 shake256(b"abc", &mut shake_out);
464 cshake256(b"abc", b"", b"", &mut cshake_out);
465 assert_eq!(
466 shake_out, cshake_out,
467 "cSHAKE256 with empty N and S must equal SHAKE256"
468 );
469 }
470
471 #[test]
472 fn cshake128_customization_matters() {
473 let mut out1 = [0u8; 32];
474 let mut out2 = [0u8; 32];
475 cshake128(b"abc", b"", b"customA", &mut out1);
476 cshake128(b"abc", b"", b"customB", &mut out2);
477 assert_ne!(
478 out1, out2,
479 "Different customization strings must produce different outputs"
480 );
481 }
482
483 #[test]
486 fn tuple_hash128_unambiguous() {
487 let mut out1 = [0u8; 32];
488 let mut out2 = [0u8; 32];
489 tuple_hash128(&[b"ab", b"c"], b"", &mut out1).unwrap();
490 tuple_hash128(&[b"a", b"bc"], b"", &mut out2).unwrap();
491 assert_ne!(
492 out1, out2,
493 "TupleHash128 must disambiguate ('ab','c') from ('a','bc')"
494 );
495 }
496
497 #[test]
498 fn tuple_hash256_unambiguous() {
499 let mut out1 = [0u8; 32];
500 let mut out2 = [0u8; 32];
501 tuple_hash256(&[b"ab", b"c"], b"", &mut out1).unwrap();
502 tuple_hash256(&[b"a", b"bc"], b"", &mut out2).unwrap();
503 assert_ne!(
504 out1, out2,
505 "TupleHash256 must disambiguate ('ab','c') from ('a','bc')"
506 );
507 }
508
509 #[test]
510 fn tuple_hash128_deterministic() {
511 let mut out1 = [0u8; 32];
512 let mut out2 = [0u8; 32];
513 tuple_hash128(&[b"hello", b"world"], b"custom", &mut out1).unwrap();
514 tuple_hash128(&[b"hello", b"world"], b"custom", &mut out2).unwrap();
515 assert_eq!(out1, out2, "TupleHash128 must be deterministic");
516 }
517
518 #[test]
519 fn tuple_hash128_customization_matters() {
520 let mut out1 = [0u8; 32];
521 let mut out2 = [0u8; 32];
522 tuple_hash128(&[b"data"], b"custA", &mut out1).unwrap();
523 tuple_hash128(&[b"data"], b"custB", &mut out2).unwrap();
524 assert_ne!(
525 out1, out2,
526 "Different customizations must produce different TupleHash128 outputs"
527 );
528 }
529
530 #[test]
533 fn left_encode_zero() {
534 assert_eq!(left_encode(0), alloc::vec![1u8, 0u8]);
535 }
536
537 #[test]
538 fn left_encode_one() {
539 assert_eq!(left_encode(1), alloc::vec![1u8, 1u8]);
541 }
542
543 #[test]
544 fn right_encode_zero() {
545 assert_eq!(right_encode(0), alloc::vec![0u8, 1u8]);
546 }
547
548 #[test]
549 fn right_encode_one() {
550 assert_eq!(right_encode(1), alloc::vec![1u8, 1u8]);
551 }
552
553 #[test]
556 fn blake2b_keyed_different_keys() {
557 let mut out1 = [0u8; 32];
558 let mut out2 = [0u8; 32];
559 blake2b_keyed(b"key1", b"message", &mut out1).unwrap();
560 blake2b_keyed(b"key2", b"message", &mut out2).unwrap();
561 assert_ne!(
562 out1, out2,
563 "Different keys must produce different BLAKE2b-keyed outputs"
564 );
565 }
566
567 #[test]
568 fn blake2b_keyed_different_messages() {
569 let mut out1 = [0u8; 32];
570 let mut out2 = [0u8; 32];
571 blake2b_keyed(b"key", b"message1", &mut out1).unwrap();
572 blake2b_keyed(b"key", b"message2", &mut out2).unwrap();
573 assert_ne!(out1, out2);
574 }
575
576 #[test]
577 fn blake2b_keyed_empty_key_rejected() {
578 let mut out = [0u8; 32];
579 assert_eq!(
580 blake2b_keyed(b"", b"msg", &mut out).unwrap_err(),
581 CryptoError::InvalidKey
582 );
583 }
584
585 #[test]
586 fn blake2b_keyed_too_long_key_rejected() {
587 let mut out = [0u8; 32];
588 assert_eq!(
589 blake2b_keyed(&[0u8; 65], b"msg", &mut out).unwrap_err(),
590 CryptoError::InvalidKey
591 );
592 }
593
594 #[test]
595 fn blake2b_keyed_empty_output_rejected() {
596 assert_eq!(
597 blake2b_keyed(b"key", b"msg", &mut []).unwrap_err(),
598 CryptoError::BadInput
599 );
600 }
601
602 #[test]
603 fn blake2b_keyed_64byte_key_ok() {
604 let mut out = [0u8; 64];
605 blake2b_keyed(&[0x42u8; 64], b"hello", &mut out).unwrap();
606 assert!(out.iter().any(|&b| b != 0));
607 }
608
609 #[test]
610 fn blake2b_keyed_struct_api() {
611 let key = b"my secret key";
612 let msg = b"hello world";
613 let mut out_fn = [0u8; 32];
614 let mut out_struct = [0u8; 32];
615
616 blake2b_keyed(key, msg, &mut out_fn).unwrap();
617 Blake2bKeyed::new(key)
618 .unwrap()
619 .hash(msg, &mut out_struct)
620 .unwrap();
621
622 assert_eq!(
623 out_fn, out_struct,
624 "Free function and struct API must agree"
625 );
626 }
627
628 #[test]
629 fn blake2b_keyed_struct_empty_key_rejected() {
630 assert_eq!(Blake2bKeyed::new(b"").unwrap_err(), CryptoError::InvalidKey);
631 }
632
633 #[cfg(feature = "std")]
636 #[test]
637 fn hash_file_sha256_matches_in_memory() {
638 use sha2::Digest;
639 use std::io::Write;
640
641 let content = b"Hello, hash_file test!";
642
643 let mut path = std::env::temp_dir();
644 path.push("oxicrypto_hash_file_test.bin");
645
646 {
647 let mut f = std::fs::File::create(&path).unwrap();
648 f.write_all(content).unwrap();
649 }
650
651 let expected = sha2::Sha256::digest(content);
652 let actual = hash_file_sha256(&path).unwrap();
653
654 assert_eq!(actual.as_slice(), expected.as_slice());
655
656 let _ = std::fs::remove_file(&path);
657 }
658
659 #[cfg(feature = "std")]
660 #[test]
661 fn hash_file_sha512_matches_in_memory() {
662 use sha2::Digest;
663 use std::io::Write;
664
665 let content = b"SHA-512 file hash test";
666
667 let mut path = std::env::temp_dir();
668 path.push("oxicrypto_hash_file_sha512_test.bin");
669
670 {
671 let mut f = std::fs::File::create(&path).unwrap();
672 f.write_all(content).unwrap();
673 }
674
675 let expected = sha2::Sha512::digest(content);
676 let actual = hash_file_sha512(&path).unwrap();
677
678 assert_eq!(actual.as_slice(), expected.as_slice());
679
680 let _ = std::fs::remove_file(&path);
681 }
682
683 #[cfg(feature = "std")]
684 #[test]
685 fn hash_file_blake3_matches_in_memory() {
686 use std::io::Write;
687
688 let content = b"BLAKE3 file hash test";
689
690 let mut path = std::env::temp_dir();
691 path.push("oxicrypto_hash_file_blake3_test.bin");
692
693 {
694 let mut f = std::fs::File::create(&path).unwrap();
695 f.write_all(content).unwrap();
696 }
697
698 let expected = *blake3::hash(content).as_bytes();
699 let actual = hash_file_blake3(&path).unwrap();
700
701 assert_eq!(actual, expected);
702
703 let _ = std::fs::remove_file(&path);
704 }
705
706 #[cfg(feature = "std")]
707 #[test]
708 fn hash_file_sha256_not_found() {
709 let path = std::env::temp_dir().join("oxicrypto_nonexistent_file_12345678.bin");
710 let err = hash_file_sha256(&path).unwrap_err();
711 assert!(matches!(err, CryptoError::Internal(_)));
712 }
713}