1#![allow(clippy::missing_errors_doc)]
2mod error;
3mod implementations;
4mod methods;
5pub use error::*;
6use ps_base64::base64;
7use ps_ecc::ReedSolomon;
8use ps_pint16::PackedInt;
9use sha2::{Digest, Sha256};
10
11#[cfg(test)]
12pub mod tests;
13
14pub const DIGEST_SIZE: usize = 32;
15pub const HASH_SIZE_BIN: usize = 48;
16pub const HASH_SIZE_COMPACT: usize = 42;
17pub const HASH_SIZE: usize = 64;
18pub const PARITY: u8 = 7;
19pub const PARITY_OFFSET: usize = 34;
20pub const PARITY_SIZE: usize = 14;
21pub const SIZE_SIZE: usize = std::mem::size_of::<u16>();
22pub const MIN_RECOVERABLE: usize = HASH_SIZE - (PARITY as usize * 8 / 6);
24pub const MIN_RECOVERABLE_BIN: usize = HASH_SIZE_BIN - (PARITY as usize);
26
27pub const RS: ReedSolomon = match ReedSolomon::new(PARITY) {
28 Ok(rs) => rs,
29 Err(_) => panic!("Failed to construct Reed-Solomon codec."),
30};
31
32#[inline]
33#[must_use]
34pub fn sha256(data: &[u8]) -> [u8; DIGEST_SIZE] {
35 let mut hasher = Sha256::new();
36
37 hasher.update(data);
38
39 let result = hasher.finalize();
40
41 result.into()
42}
43
44#[inline]
45#[must_use]
46pub fn blake3(data: &[u8]) -> blake3::Hash {
47 blake3::hash(data)
48}
49
50#[derive(Clone, Copy)]
51#[repr(transparent)]
52pub struct Hash {
53 inner: [u8; HASH_SIZE_BIN],
54}
55
56impl Hash {
57 #[allow(clippy::self_named_constructors)]
63 pub fn hash(data: impl AsRef<[u8]>) -> Result<Self, HashError> {
64 let data = data.as_ref();
65 let mut inner = [0u8; HASH_SIZE_BIN];
66
67 let sha = sha256(data);
68 let blake = blake3(data);
69
70 for i in 0..DIGEST_SIZE {
72 inner[i] = sha[i] ^ blake.as_bytes()[i];
73 }
74
75 inner[DIGEST_SIZE..PARITY_OFFSET]
77 .copy_from_slice(&PackedInt::from_usize(data.len()).to_16_bits());
78
79 let parity = RS.generate_parity(&inner[..PARITY_OFFSET])?;
81 inner[PARITY_OFFSET..].copy_from_slice(&parity);
82
83 Ok(Self { inner })
84 }
85
86 pub fn validate_bin_vec(hash: &mut Vec<u8>) -> Result<Self, HashValidationError> {
93 hash.resize(HASH_SIZE_BIN, 0xF4);
96
97 let (data, parity) = hash.split_at_mut(PARITY_OFFSET);
98
99 ReedSolomon::correct_detached_in_place(parity, data)?;
100
101 let mut inner = [0u8; HASH_SIZE_BIN];
102
103 inner.copy_from_slice(hash);
104
105 let hash = Self { inner };
106
107 Ok(hash)
108 }
109}
110impl From<Hash> for [u8; HASH_SIZE] {
111 fn from(hash: Hash) -> [u8; HASH_SIZE] {
112 base64::sized_encode(&hash.inner)
113 }
114}
115
116impl TryFrom<&[u8]> for Hash {
117 type Error = HashValidationError;
118
119 fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
120 Self::validate(value)
121 }
122}
123
124impl TryFrom<&str> for Hash {
125 type Error = HashValidationError;
126
127 fn try_from(value: &str) -> Result<Self, Self::Error> {
128 value.as_bytes().try_into()
129 }
130}
131
132#[inline]
133pub fn hash(data: impl AsRef<[u8]>) -> Result<Hash, HashError> {
134 Hash::hash(data)
135}