1use crate::tools::types::{Hash, Pow, Salt};
6use crate::tools::{hashing, tools};
7use digest::consts::{U32, U64};
8
9use digest::Digest;
10
11fn apply_hash<H>(data: &Hash) -> anyhow::Result<Hash>
12where H: Digest
13{
14 Hash::from_slice(&H::digest(data.as_ref()).as_slice()[0..32])
15}
16
17fn apply_chained_hash(algo_index: usize, hash_current: Hash) -> anyhow::Result<Hash> {
18
19 const ALGO_COUNT: usize = 17;
20 let algo_index = algo_index % ALGO_COUNT;
21
22 match algo_index {
23 0 => apply_hash::<blake2::Blake2s256>(&hash_current),
24 1 => apply_hash::<blake2::Blake2b512>(&hash_current),
25 2 => apply_hash::<sha2::Sha256>(&hash_current),
26 3 => apply_hash::<sha2::Sha384>(&hash_current),
27 4 => apply_hash::<sha2::Sha512>(&hash_current),
28 5 => apply_hash::<sha3::Sha3_256>(&hash_current),
29 6 => apply_hash::<sha3::Sha3_384>(&hash_current),
30 7 => apply_hash::<sha3::Sha3_512>(&hash_current),
31 8 => apply_hash::<sha3::Keccak256>(&hash_current),
32 9 => apply_hash::<sha3::Keccak384>(&hash_current),
33 10 => apply_hash::<sha3::Keccak512>(&hash_current),
34 11 => apply_hash::<groestl::Groestl256>(&hash_current),
35 12 => apply_hash::<groestl::Groestl512>(&hash_current),
36 13 => apply_hash::<whirlpool::Whirlpool>(&hash_current),
37 14 => apply_hash::<skein::Skein256<U32>>(&hash_current),
38 15 => apply_hash::<skein::Skein512<U64>>(&hash_current),
39
40 16 => {
42 let mut hasher = blake3::Hasher::new();
43 hasher.update(hash_current.as_ref());
44 let hash_output = hasher.finalize();
45 Hash::from_slice(&hash_output.as_bytes()[0..32])
46 },
47
48 _ => Ok(hash_current),
49 }
50}
51
52pub fn pow_compute_data_hash(datas: &[&[u8]]) -> Hash {
58 hashing::hash_multiple(datas)
59}
60
61pub fn pow_measure_from_data_hash(data_hash: &Hash, salt: &Salt) -> anyhow::Result<(Pow, Hash)> {
67 let mut data_current = hashing::hash_two(data_hash.as_ref(), salt.as_ref());
68
69 const CHAIN_LENGTH: usize = 5;
70 const MAX_REPETITIONS: usize = 2;
71
72 for _ in 0..CHAIN_LENGTH {
73 let algo_index = data_current.as_bytes()[0] as usize;
74 let repetitions = data_current.as_bytes()[1] as usize % MAX_REPETITIONS;
75
76 for _ in 0..=repetitions {
77 data_current = apply_chained_hash(algo_index, data_current)?;
78 }
79 }
80
81 let leading_zero_bits = tools::count_leading_zero_bits(data_current.as_bytes());
82 Ok((Pow(leading_zero_bits), data_current))
83}
84
85pub fn pow_measure(datas: &[&[u8]], salt: &Salt) -> anyhow::Result<(Pow, Hash)> {
86 pow_measure_from_data_hash(&pow_compute_data_hash(datas), salt)
87}
88
89#[cfg(test)]
90mod tests {
91 use crate::tools::pow::{pow_compute_data_hash, pow_measure, pow_measure_from_data_hash};
92 use crate::tools::tools;
93 use crate::tools::types::{Pow, Salt};
94
95 struct RegressionVector {
96 label: &'static str,
97 datas: Vec<Vec<u8>>,
98 salt_hex: &'static str,
99 expected_pow: u8,
100 expected_final_hash_hex: &'static str,
101 }
102
103 fn regression_vectors() -> Vec<RegressionVector> {
107 vec![
108 RegressionVector {
109 label: "empty_data_zero_salt",
110 datas: vec![hex::decode("").unwrap()],
111 salt_hex: "0000000000000000",
112 expected_pow: 1,
113 expected_final_hash_hex: "6eec432cb487409c9500776340420e6281ac4aa06c2b7ec39916828dbf8bb39e",
114 },
115 RegressionVector {
116 label: "single_byte_zero_data",
117 datas: vec![hex::decode("00").unwrap()],
118 salt_hex: "0000000000000001",
119 expected_pow: 4,
120 expected_final_hash_hex: "0db42c05e85a32ac76f14c4a3132e8b82c0f97ba49e5bf0464173067ec20e86d",
121 },
122 RegressionVector {
123 label: "single_byte_high",
124 datas: vec![hex::decode("ff").unwrap()],
125 salt_hex: "ffffffffffffffff",
126 expected_pow: 0,
127 expected_final_hash_hex: "8b3754ac665ff1cdff1539552511b23b320c9865f0989add3bcaa245798be5a8",
128 },
129 RegressionVector {
130 label: "ascii_short",
131 datas: vec![hex::decode("68617368697665727365").unwrap()],
132 salt_hex: "0123456789abcdef",
133 expected_pow: 0,
134 expected_final_hash_hex: "adb2ef187879beb0615fc054ef2c94fcda7f022226b5f86d92485c00161bf081",
135 },
136 RegressionVector {
137 label: "ascii_long",
138 datas: vec![hex::decode("54686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f67").unwrap()],
139 salt_hex: "deadbeefcafebabe",
140 expected_pow: 0,
141 expected_final_hash_hex: "9502fa8669c3a191e3c0f3a59ce5b630c97c0460262cc8235b225ec88f05b27c",
142 },
143 RegressionVector {
144 label: "two_chunks_ascii",
145 datas: vec![hex::decode("68656c6c6f").unwrap(), hex::decode("776f726c64").unwrap()],
146 salt_hex: "fedcba9876543210",
147 expected_pow: 0,
148 expected_final_hash_hex: "8885e4616f1b5657c954a587a260c05d820c028fcc792ca27741d943507c397d",
149 },
150 RegressionVector {
151 label: "three_chunks_mixed",
152 datas: vec![hex::decode("61").unwrap(), hex::decode("6262").unwrap(), hex::decode("636363").unwrap()],
153 salt_hex: "1111111111111111",
154 expected_pow: 2,
155 expected_final_hash_hex: "2ca9bb4af0b9946d7b4ec2591f80148235355c75d4a6bb808257980c2546f6a3",
156 },
157 RegressionVector {
158 label: "binary_pattern",
159 datas: vec![hex::decode("0001020304050607").unwrap()],
160 salt_hex: "8000000000000000",
161 expected_pow: 3,
162 expected_final_hash_hex: "1080bab28b0963e88416512f313172774a0b1e538912928ed870f28c402d2e29",
163 },
164 RegressionVector {
165 label: "256_byte_zeroes",
166 datas: vec![hex::decode("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap()],
167 salt_hex: "a5a5a5a5a5a5a5a5",
168 expected_pow: 2,
169 expected_final_hash_hex: "39c0c67e6d471fee89b36f484237af21a83e2ab5113ff0c7a2adc66ce95a7de9",
170 },
171 RegressionVector {
172 label: "256_byte_ones",
173 datas: vec![hex::decode("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap()],
174 salt_hex: "5a5a5a5a5a5a5a5a",
175 expected_pow: 0,
176 expected_final_hash_hex: "eb532e53c96611844a9d7cb3417740572f7455a54f5a9c77dec08d24eccb3b26",
177 },
178 ]
179 }
180
181 #[tokio::test]
182 async fn pow_test() {
183 for _ in 1..1000 {
184 let mut data1 = [0u8; 1024];
185 tools::random_fill_bytes(&mut data1);
186 let mut data2 = [0u8; 512];
187 tools::random_fill_bytes(&mut data2);
188
189 let salt = Salt::random();
190 let _pow = pow_measure(&[&data1, &data2], &salt);
191 }
192 }
193
194 #[tokio::test]
197 async fn pow_measure_and_from_data_hash_agree() -> anyhow::Result<()> {
198 for _ in 0..200 {
199 let mut data1 = [0u8; 256];
200 tools::random_fill_bytes(&mut data1);
201 let mut data2 = [0u8; 128];
202 tools::random_fill_bytes(&mut data2);
203 let salt = Salt::random();
204
205 let (pow_direct, hash_direct) = pow_measure(&[&data1, &data2], &salt)?;
206 let data_hash = pow_compute_data_hash(&[&data1, &data2]);
207 let (pow_split, hash_split) = pow_measure_from_data_hash(&data_hash, &salt)?;
208
209 assert_eq!(pow_direct, pow_split);
210 assert_eq!(hash_direct, hash_split);
211 }
212 Ok(())
213 }
214
215 #[test]
221 fn pow_regression_vectors_match() -> anyhow::Result<()> {
222 for vector in regression_vectors() {
223 let salt_bytes = hex::decode(vector.salt_hex).expect("salt_hex must be valid hex");
224 let salt = Salt::from_slice(&salt_bytes)?;
225 let data_refs: Vec<&[u8]> = vector.datas.iter().map(|v| v.as_slice()).collect();
226
227 let (pow_direct, hash_direct) = pow_measure(&data_refs, &salt)?;
228 let data_hash = pow_compute_data_hash(&data_refs);
229 let (pow_split, hash_split) = pow_measure_from_data_hash(&data_hash, &salt)?;
230
231 assert_eq!(pow_direct, pow_split, "vector {}: two-path mismatch", vector.label);
232 assert_eq!(hash_direct, hash_split, "vector {}: two-path mismatch", vector.label);
233 assert_eq!(pow_direct, Pow(vector.expected_pow), "vector {}: pow drift (likely crypto crate output change)", vector.label);
234 assert_eq!(hex::encode(hash_direct.as_bytes()), vector.expected_final_hash_hex, "vector {}: final-hash drift (likely crypto crate output change)", vector.label);
235 }
236 Ok(())
237 }
238
239
240 }