1use crate::error::CryptoError;
24
25use std::{convert::{TryFrom, TryInto}, fmt};
26
27use blake2::{
28 Digest,
29 Blake2b,
30 digest::consts::U32,
31};
32
33use subtle::{Choice, ConstantTimeEq};
34
35pub const DEFAULT_HASH_VERSION: u8 = 1;
37
38pub const MIN_HASH_VERSION: u8 = 1;
40
41pub const MAX_HASH_VERSION: u8 = 1;
43
44const V1_DIGEST_SIZE: usize = 32;
45type V1Blake = Blake2b<U32>;
46
47pub const MAX_HASH_LEN: usize = 1 + V1_DIGEST_SIZE;
49
50#[derive(Clone)]
68pub struct Hash {
69 data: [u8; MAX_HASH_LEN],
70}
71
72impl Hash {
73 pub fn new(data: impl AsRef<[u8]>) -> Self {
75 Self::with_version(data, DEFAULT_HASH_VERSION).unwrap()
76 }
77
78 pub fn with_version(data: impl AsRef<[u8]>, version: u8) -> Result<Self, CryptoError> {
82 let mut state = HashState::with_version(version)?;
83 state.update(data);
84 Ok(state.finalize())
85 }
86
87 pub fn version(&self) -> u8 {
89 self.data[0]
90 }
91
92 pub fn digest(&self) -> &[u8] {
94 &self.data[1..]
95 }
96
97 pub fn from_base58(s: &str) -> Result<Self, CryptoError> {
100 let raw = bs58::decode(s)
101 .into_vec()
102 .or(Err(CryptoError::BadFormat("Not valid Base58")))?;
103 Self::try_from(&raw[..])
104 }
105
106 pub fn to_base58(&self) -> String {
108 bs58::encode(&self.data).into_string()
109 }
110}
111
112impl TryFrom<&[u8]> for Hash {
113 type Error = CryptoError;
114
115 fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
116 let &version = value.first().ok_or(CryptoError::BadLength {
117 step: "get hash version",
118 actual: 0,
119 expected: 1,
120 })?;
121
122 if version < MIN_HASH_VERSION || version > MAX_HASH_VERSION {
124 return Err(CryptoError::UnsupportedVersion(version));
125 }
126
127 let data: [u8; MAX_HASH_LEN] = value.try_into().map_err(|_| {
129 CryptoError::BadLength {
130 step: "get hash digest (with version)",
131 actual: value.len(),
132 expected: 1 + V1_DIGEST_SIZE,
133 }
134 })?;
135
136 Ok(Self { data })
137 }
138}
139
140impl std::convert::AsRef<[u8]> for Hash {
141 fn as_ref(&self) -> &[u8] {
142 &self.data[..]
143 }
144}
145
146impl ConstantTimeEq for Hash {
147 fn ct_eq(&self, other: &Self) -> Choice {
148 self.data[..].ct_eq(&other.data[..])
149 }
150}
151
152impl PartialEq for Hash {
153 fn eq(&self, other: &Self) -> bool {
154 self.ct_eq(other).into()
155 }
156}
157
158impl Eq for Hash {}
159
160use std::cmp::Ordering;
163impl std::cmp::Ord for Hash {
164 fn cmp(&self, other: &Hash) -> Ordering {
165 self.data.cmp(&other.data)
166 }
167}
168
169impl std::cmp::PartialOrd for Hash {
170 fn partial_cmp(&self, other: &Hash) -> Option<Ordering> {
171 Some(self.cmp(other))
172 }
173}
174
175impl fmt::Debug for Hash {
176 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
177 let (version, digest) = self.data.split_first().unwrap();
178 f.debug_struct("Hash")
179 .field("version", version)
180 .field("digest", &digest)
181 .finish()
182 }
183}
184
185impl fmt::Display for Hash {
186 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187 write!(f, "{}", self.to_base58())
188 }
189}
190
191impl fmt::LowerHex for Hash {
192 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
193 for byte in self.data.iter() {
194 write!(f, "{:x}", byte)?;
195 }
196 Ok(())
197 }
198}
199
200impl fmt::UpperHex for Hash {
201 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
202 for byte in self.data.iter() {
203 write!(f, "{:X}", byte)?;
204 }
205 Ok(())
206 }
207}
208
209impl std::hash::Hash for Hash {
210 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
211 self.data.hash(state);
212 }
213}
214
215#[derive(Clone)]
231pub struct HashState {
232 state: V1Blake,
233}
234
235impl HashState {
236 pub fn new() -> HashState {
238 Self::with_version(DEFAULT_HASH_VERSION).unwrap()
239 }
240
241 pub fn with_version(version: u8) -> Result<HashState, CryptoError> {
245 if version > MAX_HASH_VERSION || version < MIN_HASH_VERSION {
246 return Err(CryptoError::UnsupportedVersion(version));
247 }
248 let state = V1Blake::new();
249 Ok(HashState { state })
250 }
251
252 pub fn version(&self) -> u8 {
254 1u8
255 }
256
257 pub fn update(&mut self, data: impl AsRef<[u8]>) {
259 self.state.update(data);
260 }
261
262 pub fn hash(&self) -> Hash {
264 self.clone().finalize()
265 }
266
267 pub fn finalize(self) -> Hash {
269 let mut data = [0u8; MAX_HASH_LEN];
270 data[0] = 1u8;
271 let hash = self.state.finalize();
272 data[1..].copy_from_slice(&hash);
273 Hash { data }
274 }
275}
276
277impl Default for HashState {
278 fn default() -> Self {
279 Self::new()
280 }
281}
282
283impl fmt::Debug for HashState {
284 fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
285 formatter
286 .debug_struct("HashState")
287 .field("version", &self.version())
288 .finish()
289 }
290}
291
292#[cfg(test)]
293mod tests {
294 use super::*;
295 use hex;
296 use serde_json::{self, Value};
297 use std::fs;
298
299 #[test]
300 fn hash_vectors() {
301 let file_ref = fs::File::open("test-resources/blake2b-test-vectors.json").unwrap();
302 let json_ref: Value = serde_json::from_reader(file_ref).unwrap();
303
304 for vector in json_ref.as_array().unwrap().iter() {
305 let ref_hash = hex::decode(vector["out"].as_str().unwrap()).unwrap();
306 let ref_input = hex::decode(vector["input"].as_str().unwrap()).unwrap();
307 let h = Hash::new(&ref_input[..]);
308 let mut state: HashState = HashState::new();
309 state.update(&ref_input[..]);
310 let h2 = state.hash();
311 let h3 = state.finalize();
312 assert_eq!(h.version(), 1u8);
313 assert_eq!(h.digest(), &ref_hash[..]);
314 assert_eq!(h2.version(), 1u8);
315 assert_eq!(h2.digest(), &ref_hash[..]);
316 assert_eq!(h3.version(), 1u8);
317 assert_eq!(h3.digest(), &ref_hash[..]);
318 let v = Vec::from(h.as_ref());
319 let hd = Hash::try_from(&v[..]).unwrap();
320 assert_eq!(h, hd);
321 }
322 }
323
324 #[test]
325 fn bad_version() {
326 let hash = Hash::new(b"I am a message, being hashed.");
327 let mut enc = Vec::from(hash.as_ref());
328 enc[0] = 99u8;
329 let result = Hash::try_from(&enc[..]);
330 assert!(result.is_err());
331 enc[0] = 0u8;
332 let result = Hash::try_from(&enc[..]);
333 assert!(result.is_err());
334 }
335
336 #[test]
337 fn edge_cases() {
338 match Hash::with_version([1, 2], 0).unwrap_err() {
339 CryptoError::UnsupportedVersion(v) => {
340 assert_eq!(v, 0, "UnsupportedVersion should have been 0");
341 }
342 _ => panic!("New hash should always fail on version 0"),
343 };
344 match HashState::with_version(0).unwrap_err() {
345 CryptoError::UnsupportedVersion(v) => {
346 assert_eq!(v, 0, "UnsupportedVersion should have been 0");
347 }
348 _ => panic!("HashState should always fail on version 0"),
349 };
350 let digest =
351 hex::decode("8b57a796a5d07cb04cc1614dfc2acb3f73edc712d7f433619ca3bbe66bb15f49")
352 .unwrap();
353 let h = Hash::new(hex::decode("00010203040506070809").unwrap());
354 assert_eq!(h.version(), 1);
355 assert_eq!(h.digest(), &digest[..]);
356 }
357
358 #[test]
359 fn base58() {
360 use rand::prelude::*;
361 let mut rng = rand::thread_rng();
362
363 let h = Hash::new(b"I am data, about to be hashed.");
365 let b58 = h.to_base58();
366 let expected = "PnQZwqcH74g1gGpsRbPpzpPqTaHU5PELxrwAosE9MWxM";
367 let eq = b58 == expected;
368 if !eq {
369 println!("Base58 actual: {}", b58);
370 println!("Base58 expected: {}", expected);
371 }
372 assert!(eq);
373 let h2 = Hash::from_base58(&b58).unwrap();
374 let eq = h == h2;
375 if !eq {
376 println!("in: {}", h);
377 println!("out: {}", h2);
378 }
379 assert!(eq);
380
381 for _ in 0..1000 {
383 let mut v: Vec<u8> = Vec::with_capacity(32);
384 for _ in 0..32 {
385 v.push(rng.gen());
386 }
387 let h = Hash::new(&v[..]);
388 let b58 = h.to_base58();
389 let h2 = Hash::from_base58(&b58).unwrap();
390 let eq = h == h2;
391 if !eq {
392 println!("in: {}", h);
393 println!("out: {}", h2);
394 }
395 assert!(eq);
396 }
397 }
398}