#![doc(html_root_url = "https://docs.rs/casper-hashing/1.4.4")]
#![doc(
html_favicon_url = "https://raw.githubusercontent.com/CasperLabs/casper-node/master/images/CasperLabs_Logo_Favicon_RGB_50px.png",
html_logo_url = "https://raw.githubusercontent.com/CasperLabs/casper-node/master/images/CasperLabs_Logo_Symbol_RGB.png",
test(attr(forbid(warnings)))
)]
#![warn(missing_docs)]
use std::{
array::TryFromSliceError,
collections::BTreeMap,
convert::{TryFrom, TryInto},
fmt::{self, Debug, Display, Formatter, LowerHex, UpperHex},
};
use blake2::{
digest::{Update, VariableOutput},
VarBlake2b,
};
use datasize::DataSize;
use itertools::Itertools;
use schemars::JsonSchema;
use serde::{de::Error as SerdeError, Deserialize, Deserializer, Serialize, Serializer};
use casper_types::{
bytesrepr::{self, FromBytes, ToBytes},
checksummed_hex,
};
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Incorrect digest length {0}, expected length {}.", Digest::LENGTH)]
IncorrectDigestLength(usize),
#[error("Base16 decode error {0}.")]
Base16DecodeError(base16::DecodeError),
}
#[derive(Copy, Clone, DataSize, Ord, PartialOrd, Eq, PartialEq, Hash, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
#[schemars(with = "String", description = "Hex-encoded hash digest.")]
pub struct Digest(#[schemars(skip, with = "String")] [u8; Digest::LENGTH]);
impl Digest {
pub const LENGTH: usize = 32;
pub const SENTINEL_NONE: Digest = Digest([0u8; Digest::LENGTH]);
pub const SENTINEL_RFOLD: Digest = Digest([1u8; Digest::LENGTH]);
pub const SENTINEL_MERKLE_TREE: Digest = Digest([2u8; Digest::LENGTH]);
pub fn hash<T: AsRef<[u8]>>(data: T) -> Digest {
let mut ret = [0u8; Digest::LENGTH];
let mut hasher = VarBlake2b::new(Digest::LENGTH).unwrap();
hasher.update(data);
hasher.finalize_variable(|hash| ret.clone_from_slice(hash));
Digest(ret)
}
pub fn hash_pair<T: AsRef<[u8]>, U: AsRef<[u8]>>(data1: T, data2: U) -> Digest {
let mut result = [0; Digest::LENGTH];
let mut hasher = VarBlake2b::new(Digest::LENGTH).unwrap();
hasher.update(data1);
hasher.update(data2);
hasher.finalize_variable(|slice| {
result.copy_from_slice(slice);
});
Digest(result)
}
pub fn value(&self) -> [u8; Digest::LENGTH] {
self.0
}
pub fn into_vec(self) -> Vec<u8> {
self.0.to_vec()
}
pub fn hash_vec_merkle_tree(vec: Vec<Digest>) -> Digest {
vec.into_iter()
.tree_fold1(|x, y| Digest::hash_pair(&x, &y))
.unwrap_or(Self::SENTINEL_MERKLE_TREE)
}
pub fn hash_btree_map<K, V>(btree_map: &BTreeMap<K, V>) -> Result<Digest, bytesrepr::Error>
where
K: ToBytes,
V: ToBytes,
{
let mut kv_hashes: Vec<Digest> = Vec::with_capacity(btree_map.len());
for (key, value) in btree_map.iter() {
kv_hashes.push(Digest::hash_pair(
&Digest::hash(key.to_bytes()?),
&Digest::hash(value.to_bytes()?),
))
}
Ok(Self::hash_vec_merkle_tree(kv_hashes))
}
pub fn hash_slice_rfold(slice: &[Digest]) -> Digest {
Self::hash_slice_with_proof(slice, Self::SENTINEL_RFOLD)
}
pub fn hash_slice_with_proof(slice: &[Digest], proof: Digest) -> Digest {
slice
.iter()
.rfold(proof, |prev, next| Digest::hash_pair(next, &prev))
}
pub fn from_hex<T: AsRef<[u8]>>(hex_input: T) -> Result<Self, Error> {
let bytes = checksummed_hex::decode(&hex_input).map_err(Error::Base16DecodeError)?;
let slice: [u8; Self::LENGTH] = bytes
.try_into()
.map_err(|_| Error::IncorrectDigestLength(hex_input.as_ref().len()))?;
Ok(Digest(slice))
}
}
impl LowerHex for Digest {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let hex_string = base16::encode_lower(&self.value());
if f.alternate() {
write!(f, "0x{}", hex_string)
} else {
write!(f, "{}", hex_string)
}
}
}
impl UpperHex for Digest {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let hex_string = base16::encode_upper(&self.value());
if f.alternate() {
write!(f, "0x{}", hex_string)
} else {
write!(f, "{}", hex_string)
}
}
}
impl Display for Digest {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{:10}", base16::encode_lower(&self.0))
}
}
impl Debug for Digest {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", base16::encode_lower(&self.0))
}
}
impl From<[u8; Digest::LENGTH]> for Digest {
fn from(arr: [u8; Digest::LENGTH]) -> Self {
Digest(arr)
}
}
impl<'a> TryFrom<&'a [u8]> for Digest {
type Error = TryFromSliceError;
fn try_from(slice: &[u8]) -> Result<Digest, Self::Error> {
<[u8; Digest::LENGTH]>::try_from(slice).map(Digest)
}
}
impl AsRef<[u8]> for Digest {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
impl From<Digest> for [u8; Digest::LENGTH] {
fn from(hash: Digest) -> Self {
hash.0
}
}
impl ToBytes for Digest {
#[inline(always)]
fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
self.0.to_bytes()
}
#[inline(always)]
fn serialized_length(&self) -> usize {
self.0.serialized_length()
}
}
impl FromBytes for Digest {
#[inline(always)]
fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
FromBytes::from_bytes(bytes).map(|(arr, rem)| (Digest(arr), rem))
}
}
impl Serialize for Digest {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
if serializer.is_human_readable() {
base16::encode_lower(&self.0).serialize(serializer)
} else {
(&self.0[..]).serialize(serializer)
}
}
}
impl<'de> Deserialize<'de> for Digest {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
if deserializer.is_human_readable() {
let hex_string = String::deserialize(deserializer)?;
let bytes =
checksummed_hex::decode(hex_string.as_bytes()).map_err(SerdeError::custom)?;
let data =
<[u8; Digest::LENGTH]>::try_from(bytes.as_ref()).map_err(SerdeError::custom)?;
Ok(Digest::from(data))
} else {
let data = <Vec<u8>>::deserialize(deserializer)?;
Digest::try_from(data.as_slice()).map_err(D::Error::custom)
}
}
}
#[cfg(test)]
mod test {
use std::iter;
use proptest_attr_macro::proptest;
use super::*;
#[proptest]
fn bytesrepr_roundtrip(data: [u8; Digest::LENGTH]) {
let hash = Digest(data);
bytesrepr::test_serialization_roundtrip(&hash);
}
#[test]
fn blake2b_hash_known() {
let inputs_and_digests = [
(
"",
"0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8",
),
(
"abc",
"bddd813c634239723171ef3fee98579b94964e3bb1cb3e427262c8c068d52319",
),
(
"The quick brown fox jumps over the lazy dog",
"01718cec35cd3d796dd00020e0bfecb473ad23457d063b75eff29c0ffa2e58a9",
),
];
for (known_input, expected_digest) in &inputs_and_digests {
let known_input: &[u8] = known_input.as_ref();
assert_eq!(*expected_digest, format!("{:?}", Digest::hash(known_input)));
}
}
#[test]
fn from_valid_hex_should_succeed() {
for char in "abcdefABCDEF0123456789".chars() {
let input: String = iter::repeat(char).take(64).collect();
assert!(Digest::from_hex(input).is_ok());
}
}
#[test]
fn from_hex_invalid_length_should_fail() {
for len in &[2_usize, 62, 63, 65, 66] {
let input: String = "f".repeat(*len);
assert!(Digest::from_hex(input).is_err());
}
}
#[test]
fn from_hex_invalid_char_should_fail() {
for char in "g %-".chars() {
let input: String = iter::repeat('f').take(63).chain(iter::once(char)).collect();
assert!(Digest::from_hex(input).is_err());
}
}
#[test]
fn should_display_digest_in_hex() {
let hash = Digest([0u8; 32]);
let hash_hex = format!("{:?}", hash);
assert_eq!(
hash_hex,
"0000000000000000000000000000000000000000000000000000000000000000"
);
}
#[test]
fn should_print_digest_lower_hex() {
let hash = Digest([10u8; 32]);
let hash_lower_hex = format!("{:x}", hash);
assert_eq!(
hash_lower_hex,
"0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a"
)
}
#[test]
fn should_print_digest_upper_hex() {
let hash = Digest([10u8; 32]);
let hash_upper_hex = format!("{:X}", hash);
assert_eq!(
hash_upper_hex,
"0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A"
)
}
#[test]
fn alternate_should_prepend_0x() {
let hash = Digest([0u8; 32]);
let hash_hex_alt = format!("{:#x}", hash);
assert_eq!(
hash_hex_alt,
"0x0000000000000000000000000000000000000000000000000000000000000000"
)
}
#[test]
fn test_hash_pair() {
let hash1 = Digest([1u8; 32]);
let hash2 = Digest([2u8; 32]);
let hash = Digest::hash_pair(&hash1, &hash2);
let hash_lower_hex = format!("{:x}", hash);
assert_eq!(
hash_lower_hex,
"30b600fb1f0cc0b3f0fc28cdcb7389405a6659be81c7d5c5905725aa3a5119ce"
);
}
#[test]
fn test_hash_rfold() {
let hashes = vec![
Digest([1u8; 32]),
Digest([2u8; 32]),
Digest([3u8; 32]),
Digest([4u8; 32]),
Digest([5u8; 32]),
];
let hash = Digest::hash_slice_rfold(&hashes[..]);
let hash_lower_hex = format!("{:x}", hash);
assert_eq!(
hash_lower_hex,
"e137f4eb94d2387065454eecfe2cdb5584e3dbd5f1ca07fc511fffd13d234e8e"
);
let proof = Digest::hash_slice_rfold(&hashes[2..]);
let hash_proof = Digest::hash_slice_with_proof(&hashes[..2], proof);
assert_eq!(hash, hash_proof);
}
#[test]
fn test_hash_merkle_odd() {
let hashes = vec![
Digest([1u8; 32]),
Digest([2u8; 32]),
Digest([3u8; 32]),
Digest([4u8; 32]),
Digest([5u8; 32]),
];
let hash = Digest::hash_vec_merkle_tree(hashes);
let hash_lower_hex = format!("{:x}", hash);
assert_eq!(
hash_lower_hex,
"c18aaf359f7b4643991f68fbfa8c503eb460da497399cdff7d8a2b1bc4399589"
);
}
#[test]
fn test_hash_merkle_even() {
let hashes = vec![
Digest([1u8; 32]),
Digest([2u8; 32]),
Digest([3u8; 32]),
Digest([4u8; 32]),
Digest([5u8; 32]),
Digest([6u8; 32]),
];
let hash = Digest::hash_vec_merkle_tree(hashes);
let hash_lower_hex = format!("{:x}", hash);
assert_eq!(
hash_lower_hex,
"0470ecc8abdcd6ecd3a4c574431b80bb8751c7a43337d5966dadf07899f8804b"
);
}
#[test]
fn test_hash_btreemap() {
let mut map = BTreeMap::new();
let _ = map.insert(Digest([1u8; 32]), Digest([2u8; 32]));
let _ = map.insert(Digest([3u8; 32]), Digest([4u8; 32]));
let _ = map.insert(Digest([5u8; 32]), Digest([6u8; 32]));
let _ = map.insert(Digest([7u8; 32]), Digest([8u8; 32]));
let _ = map.insert(Digest([9u8; 32]), Digest([10u8; 32]));
let hash = Digest::hash_btree_map(&map).unwrap();
let hash_lower_hex = format!("{:x}", hash);
assert_eq!(
hash_lower_hex,
"f3bc94beb2470d5c09f575b439d5f238bdc943233774c7aa59e597cc2579e148"
);
}
#[test]
fn digest_deserialize_regression() {
let input = Digest([0; 32]);
let serialized = bincode::serialize(&input).expect("failed to serialize.");
let expected = vec![
32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
assert_eq!(expected, serialized);
}
}