Skip to main content

midnight_base_crypto/
hash.rs

1// This file is part of midnight-ledger.
2// Copyright (C) 2025 Midnight Foundation
3// SPDX-License-Identifier: Apache-2.0
4// Licensed under the Apache License, Version 2.0 (the "License");
5// You may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7// http://www.apache.org/licenses/LICENSE-2.0
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14//! Hashing functions for use across Midnight.
15
16use crate::repr::{BinaryHashRepr, MemWrite};
17use const_hex::ToHexExt;
18use fake::Dummy;
19#[cfg(feature = "proptest")]
20use proptest_derive::Arbitrary;
21use serde::{Deserialize, Serialize};
22use serialize::{Deserializable, Serializable, Tagged, tag_enforcement_test};
23use sha2::{Digest, Sha256};
24use std::fmt::{self, Debug, Display, Formatter};
25use std::io;
26use zeroize::Zeroize;
27
28/// The number of bytes output by [`persistent_hash`].
29pub const PERSISTENT_HASH_BYTES: usize = 32;
30
31/// A wrapper around hash outputs.
32#[derive(
33    Copy,
34    Clone,
35    Default,
36    PartialOrd,
37    Ord,
38    Hash,
39    BinaryHashRepr,
40    Serializable,
41    Serialize,
42    Deserialize,
43    Dummy,
44    Zeroize,
45)]
46#[cfg_attr(feature = "proptest", derive(Arbitrary))]
47#[allow(clippy::derived_hash_with_manual_eq)]
48pub struct HashOutput(pub [u8; PERSISTENT_HASH_BYTES]);
49tag_enforcement_test!(HashOutput);
50
51impl PartialEq for HashOutput {
52    fn eq(&self, other: &Self) -> bool {
53        subtle::ConstantTimeEq::ct_eq(&self.0[..], &other.0[..]).into()
54    }
55}
56
57impl Eq for HashOutput {}
58
59impl Tagged for HashOutput {
60    fn tag() -> std::borrow::Cow<'static, str> {
61        <[u8; PERSISTENT_HASH_BYTES]>::tag()
62    }
63    fn tag_unique_factor() -> String {
64        <[u8; PERSISTENT_HASH_BYTES]>::tag_unique_factor()
65    }
66}
67
68#[cfg(feature = "proptest")]
69serialize::randomised_serialization_test!(HashOutput);
70
71/// A zeroed [`HashOutput`].
72pub const BLANK_HASH: HashOutput = HashOutput([0u8; PERSISTENT_HASH_BYTES]);
73
74impl rand::distributions::Distribution<HashOutput> for rand::distributions::Standard {
75    fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> HashOutput {
76        HashOutput(rng.r#gen())
77    }
78}
79
80impl Debug for HashOutput {
81    fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
82        write!(formatter, "{}", self.0.encode_hex())
83    }
84}
85
86impl Display for HashOutput {
87    fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
88        write!(formatter, "{}", &self.0.encode_hex()[..10])
89    }
90}
91
92/// A hash function that is guaranteed for long-term support.
93pub fn persistent_hash(a: &[u8]) -> HashOutput {
94    HashOutput(Sha256::digest(a).into())
95}
96
97/// Commits to a value using `persistent_hash`.
98pub fn persistent_commit<T: BinaryHashRepr + ?Sized>(value: &T, opening: HashOutput) -> HashOutput {
99    let mut writer = PersistentHashWriter::new();
100    opening.binary_repr(&mut writer);
101    value.binary_repr(&mut writer);
102    writer.finalize()
103}
104
105/// A writer object for building large persistent commitments of data.
106pub struct PersistentHashWriter(Sha256);
107
108impl MemWrite<u8> for PersistentHashWriter {
109    fn write(&mut self, buf: &[u8]) {
110        self.0.update(buf);
111    }
112}
113
114impl io::Write for PersistentHashWriter {
115    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
116        self.0.update(buf);
117        Ok(buf.len())
118    }
119
120    fn flush(&mut self) -> io::Result<()> {
121        Ok(())
122    }
123}
124
125impl Default for PersistentHashWriter {
126    fn default() -> Self {
127        PersistentHashWriter(Sha256::new())
128    }
129}
130
131impl PersistentHashWriter {
132    /// Initializes a black hasher.
133    pub fn new() -> Self {
134        Default::default()
135    }
136
137    /// Finalizes the hasher, and returns the result.
138    pub fn finalize(self) -> HashOutput {
139        HashOutput(self.0.finalize().into())
140    }
141}