use core::convert::TryFrom;
use core::fmt;
use core::fmt::Debug;
use core::fmt::Display;
use core::hash::Hash;
use core::ops::Deref;
use core::str::FromStr;
use bitcoin_hashes::sha256;
use bitcoin_hashes::sha512_256;
use bitcoin_hashes::HashEngine;
use hex_conservative::decode_to_array;
use hex_conservative::DecodeFixedLengthBytesError;
#[cfg(feature = "with-serde")]
use serde::Deserialize;
#[cfg(feature = "with-serde")]
use serde::Serialize;
use crate::prelude::*;
pub trait AccumulatorHash: Copy + Clone + Ord + Debug + Display + Hash + Default + 'static {
fn is_empty(&self) -> bool;
fn empty() -> Self;
fn is_placeholder(&self) -> bool;
fn placeholder() -> Self;
fn parent_hash(left: &Self, right: &Self) -> Self;
fn write<W>(&self, writer: &mut W) -> io::Result<()>
where
W: Write;
fn read<R>(reader: &mut R) -> io::Result<Self>
where
R: Read;
}
#[derive(Eq, PartialEq, Copy, Clone, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))]
#[derive(Default)]
pub enum BitcoinNodeHash {
#[default]
Empty,
Placeholder,
Some([u8; 32]),
}
#[deprecated(since = "0.4.0", note = "Please use BitcoinNodeHash instead.")]
pub type NodeHash = BitcoinNodeHash;
impl Deref for BitcoinNodeHash {
type Target = [u8; 32];
fn deref(&self) -> &Self::Target {
match self {
Self::Some(ref inner) => inner,
_ => &[0; 32],
}
}
}
impl Display for BitcoinNodeHash {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
if let Self::Some(ref inner) = self {
let mut s = String::new();
for byte in inner.iter() {
s.push_str(&format!("{byte:02x}"));
}
write!(f, "{s}")
} else {
write!(f, "empty")
}
}
}
impl Debug for BitcoinNodeHash {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match self {
Self::Empty => write!(f, "empty"),
Self::Placeholder => write!(f, "placeholder"),
Self::Some(ref inner) => {
let mut s = String::new();
for byte in inner.iter() {
s.push_str(&format!("{byte:02x}"));
}
write!(f, "{s}")
}
}
}
}
impl From<sha512_256::Hash> for BitcoinNodeHash {
fn from(hash: sha512_256::Hash) -> Self {
Self::Some(hash.to_byte_array())
}
}
impl From<[u8; 32]> for BitcoinNodeHash {
fn from(hash: [u8; 32]) -> Self {
Self::Some(hash)
}
}
impl From<&[u8; 32]> for BitcoinNodeHash {
fn from(hash: &[u8; 32]) -> Self {
Self::Some(*hash)
}
}
#[cfg(test)]
impl TryFrom<&str> for BitcoinNodeHash {
type Error = DecodeFixedLengthBytesError;
fn try_from(hash: &str) -> Result<Self, Self::Error> {
if hash == "0000000000000000000000000000000000000000000000000000000000000000" {
return Ok(Self::Empty);
}
let hash = decode_to_array(hash)?;
Ok(Self::Some(hash))
}
}
#[cfg(not(test))]
impl TryFrom<&str> for BitcoinNodeHash {
type Error = DecodeFixedLengthBytesError;
fn try_from(hash: &str) -> Result<Self, Self::Error> {
let inner = decode_to_array(hash)?;
Ok(Self::Some(inner))
}
}
impl From<&[u8]> for BitcoinNodeHash {
fn from(hash: &[u8]) -> Self {
let mut inner = [0; 32];
inner.copy_from_slice(hash);
Self::Some(inner)
}
}
impl From<sha256::Hash> for BitcoinNodeHash {
fn from(hash: sha256::Hash) -> Self {
Self::Some(hash.to_byte_array())
}
}
impl FromStr for BitcoinNodeHash {
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::try_from(s)
}
type Err = DecodeFixedLengthBytesError;
}
impl BitcoinNodeHash {
pub fn new(inner: [u8; 32]) -> Self {
Self::Some(inner)
}
}
impl AccumulatorHash for BitcoinNodeHash {
fn is_empty(&self) -> bool {
matches!(self, Self::Empty)
}
fn empty() -> Self {
Self::Empty
}
fn parent_hash(left: &Self, right: &Self) -> Self {
let mut hash = sha512_256::Hash::engine();
hash.input(&**left);
hash.input(&**right);
sha512_256::Hash::from_engine(hash).into()
}
fn is_placeholder(&self) -> bool {
matches!(self, Self::Placeholder)
}
fn placeholder() -> Self {
Self::Placeholder
}
fn write<W>(&self, writer: &mut W) -> io::Result<()>
where
W: Write,
{
match self {
Self::Empty => writer.write_all(&[0]),
Self::Placeholder => writer.write_all(&[1]),
Self::Some(hash) => {
writer.write_all(&[2])?;
writer.write_all(hash)
}
}
}
fn read<R>(reader: &mut R) -> io::Result<Self>
where
R: Read,
{
let mut tag = [0];
reader.read_exact(&mut tag)?;
match tag {
[0] => Ok(Self::Empty),
[1] => Ok(Self::Placeholder),
[2] => {
let mut hash = [0; 32];
reader.read_exact(&mut hash)?;
Ok(Self::Some(hash))
}
[_] => {
let err = io::Error::new(
io::ErrorKind::InvalidData,
"unexpected tag for AccumulatorHash",
);
Err(err)
}
}
}
}
#[cfg(test)]
mod test {
use alloc::string::ToString;
use core::str::FromStr;
use super::AccumulatorHash;
use crate::node_hash::BitcoinNodeHash;
use crate::util::hash_from_u8;
#[test]
fn test_parent_hash() {
let hash1 = hash_from_u8(0);
let hash2 = hash_from_u8(1);
let parent_hash = BitcoinNodeHash::parent_hash(&hash1, &hash2);
assert_eq!(
parent_hash.to_string().as_str(),
"02242b37d8e851f1e86f46790298c7097df06893d6226b7c1453c213e91717de"
);
}
#[test]
fn test_hash_from_str() {
let hash = BitcoinNodeHash::from_str(
"6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d",
)
.unwrap();
assert_eq!(hash, hash_from_u8(0));
}
#[test]
fn test_empty_hash() {
let hash = BitcoinNodeHash::from_str(
"0000000000000000000000000000000000000000000000000000000000000000",
)
.unwrap();
assert_eq!(hash, AccumulatorHash::empty());
}
}