1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395
// Copyright (c) 2022 MASSA LABS <info@massa.net>
use crate::error::MassaHashError;
use crate::settings::HASH_SIZE_BYTES;
use massa_serialization::{Deserializer, SerializeError, Serializer};
use nom::{
error::{context, ContextError, ParseError},
IResult,
};
use std::{cmp::Ordering, convert::TryInto, str::FromStr};
/// Hash wrapper, the underlying hash type is `Blake3`
///
/// The motivations for selecting Blake3 were-
/// Speed: Blake3 is significantly faster than other popular hashing algorithms, such as SHA-256 and SHA-3.
/// This is largely due to its ability to leverage modern CPU architectures and instruction sets, as well as its optimized implementation.
///
/// Security: Blake3 is designed to be highly secure and resistant to a wide range of attacks, including collision attacks,
/// length-extension attacks, and timing attacks. It also offers better resistance to side-channel attacks than many other hashing algorithms.
///
/// Flexibility: Blake3 is highly flexible and can be used in a variety of applications, including as a general-purpose hash function,
/// as a key derivation function, and as a message authentication code. It also supports a wide range of input sizes and can produce output
/// of any desired length.
/// Scalability: Blake3 can efficiently take advantage of multiple cores and SIMD (single instruction, multiple data) instructions,
/// allowing it to scale well on modern CPUs.
///
/// Improved Compression Function: The compression function used in Blake3 is an improved version of the one used in its predecessor, Blake2.
/// This improved compression function offers better diffusion and mixing properties, which contributes to its increased security.
///
/// Keyed Hashing: Blake3 supports keyed hashing, which allows users to use a secret key to generate a unique hash value.
/// This feature can be useful in applications that require message authentication or integrity verification.
///
/// Tree Hashing: Blake3 supports tree hashing, which allows users to hash large files or data structures in a parallel and efficient manner.
/// This feature can be useful in applications that involve large-scale data processing, such as cloud storage or distributed file systems.
///
/// Open Source: Blake3 is an open-source algorithm, which means that its code is publicly available for review and auditing by anyone.
/// This can help improve its security and reliability, as well as increase transparency and trust among users.
///
#[derive(Eq, PartialEq, Copy, Clone, Hash)]
pub struct Hash(blake3::Hash);
impl PartialOrd for Hash {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.0.as_bytes().cmp(other.0.as_bytes()))
}
}
/// In massa, this function is generally useful for data structures that performs ordering and where hashes are used
/// as keys. For e.g., it is used for the BTreeMap where the order of the addresses is to be maintained.
/// This function helps to have a single coherent BTreeMap which is then used to perform the draw
/// See Pos-Worker for more details.
impl Ord for Hash {
fn cmp(&self, other: &Self) -> Ordering {
self.0.as_bytes().cmp(other.0.as_bytes())
}
}
impl std::fmt::Display for Hash {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.to_bs58_check())
}
}
impl std::fmt::Debug for Hash {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
std::fmt::Display::fmt(self, f)
}
}
impl Hash {
/// Creates a hash full of zeros bytes.
pub fn zero() -> Self {
Hash(blake3::Hash::from([0; HASH_SIZE_BYTES]))
}
/// Compute a hash from data.
///
/// # Example
/// ```
/// # use massa_hash::Hash;
/// let hash = Hash::compute_from(&"hello world".as_bytes());
/// ```
pub fn compute_from(data: &[u8]) -> Self {
Hash(blake3::hash(data))
}
/// Compute a hash from tuple of byte arrays.
///
/// # Example
/// ```
/// # use massa_hash::Hash;
/// let hash = Hash::compute_from_tuple(&[&"hello".as_bytes(), &"world".as_bytes()]);
/// ```
pub fn compute_from_tuple(data: &[&[u8]]) -> Self {
let mut hasher = blake3::Hasher::new();
for d in data {
hasher.update(&(d.len() as u64).to_be_bytes());
hasher.update(d);
}
Hash(hasher.finalize())
}
/// Serialize a Hash using `bs58` encoding with checksum.
///
/// # Example
/// ```
/// # use massa_hash::Hash;
/// let hash = Hash::compute_from(&"hello world".as_bytes());
/// let serialized: String = hash.to_bs58_check();
/// ```
/// Motivations for using base58 encoding:
///
/// base58_check is like base64 but-
/// * fully standardized (no = vs /)
/// * no weird characters (eg. +) only alphanumeric
/// * ambiguous letters combined (eg. O vs 0, or l vs 1)
/// * contains a checksum at the end to detect typing errors
///
pub fn to_bs58_check(&self) -> String {
bs58::encode(self.to_bytes()).with_check().into_string()
}
/// Serialize a Hash as bytes.
///
/// # Example
/// ```
/// # use massa_hash::Hash;
/// let hash = Hash::compute_from(&"hello world".as_bytes());
/// let serialized = hash.to_bytes();
/// ```
pub fn to_bytes(&self) -> &[u8; HASH_SIZE_BYTES] {
self.0.as_bytes()
}
/// Convert into bytes.
///
/// # Example
/// ```
/// # use massa_hash::Hash;
/// let hash = Hash::compute_from(&"hello world".as_bytes());
/// let serialized = hash.into_bytes();
/// ```
pub fn into_bytes(self) -> [u8; HASH_SIZE_BYTES] {
*self.0.as_bytes()
}
/// Deserialize using `bs58` encoding with checksum.
///
/// # Example
/// ```
/// # use serde::{Deserialize, Serialize};
/// # use massa_hash::Hash;
/// let hash = Hash::compute_from(&"hello world".as_bytes());
/// let serialized: String = hash.to_bs58_check();
/// let deserialized: Hash = Hash::from_bs58_check(&serialized).unwrap();
/// ```
pub fn from_bs58_check(data: &str) -> Result<Hash, MassaHashError> {
let decoded_bs58_check = bs58::decode(data)
.with_check(None)
.into_vec()
.map_err(|err| MassaHashError::ParsingError(format!("{}", err)))?;
Ok(Hash::from_bytes(
&decoded_bs58_check
.as_slice()
.try_into()
.map_err(|err| MassaHashError::ParsingError(format!("{}", err)))?,
))
}
/// Deserialize a Hash as bytes.
///
/// # Example
/// ```
/// # use serde::{Deserialize, Serialize};
/// # use massa_hash::Hash;
/// let hash = Hash::compute_from(&"hello world".as_bytes());
/// let serialized = hash.into_bytes();
/// let deserialized: Hash = Hash::from_bytes(&serialized);
/// ```
pub fn from_bytes(data: &[u8; HASH_SIZE_BYTES]) -> Hash {
Hash(blake3::Hash::from(*data))
}
}
impl TryFrom<&[u8]> for Hash {
type Error = MassaHashError;
/// Try parsing from byte slice.
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
Ok(Hash::from_bytes(value.try_into().map_err(|err| {
MassaHashError::ParsingError(format!("{}", err))
})?))
}
}
/// Serializer for `Hash`
#[derive(Default, Clone)]
pub struct HashSerializer;
impl HashSerializer {
/// Creates a serializer for `Hash`
pub const fn new() -> Self {
Self
}
}
impl Serializer<Hash> for HashSerializer {
fn serialize(&self, value: &Hash, buffer: &mut Vec<u8>) -> Result<(), SerializeError> {
buffer.extend(value.to_bytes());
Ok(())
}
}
/// Deserializer for `Hash`
#[derive(Default, Clone)]
pub struct HashDeserializer;
impl HashDeserializer {
/// Creates a deserializer for `Hash`
pub const fn new() -> Self {
Self
}
}
impl Deserializer<Hash> for HashDeserializer {
/// ## Example
/// ```rust
/// use massa_hash::{Hash, HashDeserializer};
/// use massa_serialization::{Serializer, Deserializer, DeserializeError};
///
/// let hash_deserializer = HashDeserializer::new();
/// let hash = Hash::compute_from(&"hello world".as_bytes());
/// let (rest, deserialized) = hash_deserializer.deserialize::<DeserializeError>(hash.to_bytes()).unwrap();
/// assert_eq!(deserialized, hash);
/// assert_eq!(rest.len(), 0);
/// ```
fn deserialize<'a, E: ParseError<&'a [u8]> + ContextError<&'a [u8]>>(
&self,
buffer: &'a [u8],
) -> IResult<&'a [u8], Hash, E> {
context("Failed hash deserialization", |input: &'a [u8]| {
if buffer.len() < HASH_SIZE_BYTES {
return Err(nom::Err::Error(ParseError::from_error_kind(
input,
nom::error::ErrorKind::LengthValue,
)));
}
Ok((
&buffer[HASH_SIZE_BYTES..],
Hash::from_bytes(&buffer[..HASH_SIZE_BYTES].try_into().map_err(|_| {
nom::Err::Error(ParseError::from_error_kind(
input,
nom::error::ErrorKind::Fail,
))
})?),
))
})(buffer)
}
}
impl ::serde::Serialize for Hash {
/// `::serde::Serialize` trait for Hash
/// if the serializer is human readable,
/// serialization is done using `serialize_bs58_check`
/// else, it uses `serialize_binary`
///
/// # Example
///
/// Human readable serialization :
/// ```
/// # use serde::{Deserialize, Serialize};
/// # use massa_hash::Hash;
/// let hash = Hash::compute_from(&"hello world".as_bytes());
/// let serialized: String = serde_json::to_string(&hash).unwrap();
/// ```
///
fn serialize<S: ::serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
if s.is_human_readable() {
s.collect_str(&self.to_bs58_check())
} else {
s.serialize_bytes(self.to_bytes())
}
}
}
impl<'de> ::serde::Deserialize<'de> for Hash {
/// `::serde::Deserialize` trait for Hash
/// if the deserializer is human readable,
/// deserialization is done using `deserialize_bs58_check`
/// else, it uses `deserialize_binary`
///
/// # Example
///
/// Human readable deserialization :
/// ```
/// # use massa_hash::Hash;
/// # use serde::{Deserialize, Serialize};
/// let hash = Hash::compute_from(&"hello world".as_bytes());
/// let serialized: String = serde_json::to_string(&hash).unwrap();
/// let deserialized: Hash = serde_json::from_str(&serialized).unwrap();
/// ```
///
fn deserialize<D: ::serde::Deserializer<'de>>(d: D) -> Result<Hash, D::Error> {
if d.is_human_readable() {
struct Base58CheckVisitor;
impl<'de> ::serde::de::Visitor<'de> for Base58CheckVisitor {
type Value = Hash;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("an ASCII base58check string")
}
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: ::serde::de::Error,
{
if let Ok(v_str) = std::str::from_utf8(v) {
Hash::from_bs58_check(v_str).map_err(E::custom)
} else {
Err(E::invalid_value(::serde::de::Unexpected::Bytes(v), &self))
}
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: ::serde::de::Error,
{
Hash::from_bs58_check(v).map_err(E::custom)
}
}
d.deserialize_str(Base58CheckVisitor)
} else {
struct BytesVisitor;
impl<'de> ::serde::de::Visitor<'de> for BytesVisitor {
type Value = Hash;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a bytestring")
}
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: ::serde::de::Error,
{
Ok(Hash::from_bytes(v.try_into().map_err(E::custom)?))
}
}
d.deserialize_bytes(BytesVisitor)
}
}
}
impl FromStr for Hash {
type Err = MassaHashError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Hash::from_bs58_check(s)
}
}
#[cfg(test)]
mod tests {
use serial_test::serial;
use super::*;
fn example() -> Hash {
Hash::compute_from("hello world".as_bytes())
}
#[test]
#[serial]
fn test_serde_json() {
let hash = example();
let serialized = serde_json::to_string(&hash).unwrap();
let deserialized = serde_json::from_str(&serialized).unwrap();
assert_eq!(hash, deserialized)
}
#[test]
#[serial]
fn test_hash() {
let data = "abc".as_bytes();
let hash = Hash::compute_from(data);
let hash_ref: [u8; HASH_SIZE_BYTES] = [
100, 55, 179, 172, 56, 70, 81, 51, 255, 182, 59, 117, 39, 58, 141, 181, 72, 197, 88,
70, 93, 121, 219, 3, 253, 53, 156, 108, 213, 189, 157, 133,
];
assert_eq!(hash.into_bytes(), hash_ref);
}
}