linera_base/crypto/
hash.rs

1// Copyright (c) Facebook, Inc. and its affiliates.
2// Copyright (c) Zefchain Labs, Inc.
3// SPDX-License-Identifier: Apache-2.0
4
5//! Defines hashing primitives used by the Linera protocol.
6
7#[cfg(with_testing)]
8use std::ops::RangeInclusive;
9use std::{borrow::Cow, fmt, io, str::FromStr};
10
11#[cfg(with_testing)]
12use alloy_primitives::FixedBytes;
13use alloy_primitives::{Keccak256, B256};
14use linera_witty::{
15    GuestPointer, HList, InstanceWithMemory, Layout, Memory, Runtime, RuntimeError, RuntimeMemory,
16    WitLoad, WitStore, WitType,
17};
18#[cfg(with_testing)]
19use proptest::{
20    collection::{vec, VecStrategy},
21    prelude::{Arbitrary, Strategy},
22    strategy,
23};
24use serde::{Deserialize, Serialize};
25
26use crate::{
27    crypto::{BcsHashable, CryptoError, Hashable},
28    doc_scalar,
29};
30
31/// A Keccak256 value.
32#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Hash)]
33#[cfg_attr(with_testing, derive(Default))]
34pub struct CryptoHash(B256);
35
36impl CryptoHash {
37    /// Computes a hash.
38    pub fn new<'de, T: BcsHashable<'de>>(value: &T) -> Self {
39        let mut hasher = Keccak256Ext(Keccak256::new());
40        value.write(&mut hasher);
41        CryptoHash(hasher.0.finalize())
42    }
43
44    /// Reads the bytes of the hash value.
45    pub fn as_bytes(&self) -> &B256 {
46        &self.0
47    }
48
49    /// Force the last 12 bytes of the hash to be zeroes. This is currently used for EVM compatibility
50    pub fn make_evm_compatible(&mut self) {
51        self.0[20..32].fill(0);
52    }
53
54    /// Returns the hash of `TestString(s)`, for testing purposes.
55    #[cfg(with_testing)]
56    pub fn test_hash(s: impl Into<String>) -> Self {
57        use crate::crypto::TestString;
58
59        CryptoHash::new(&TestString::new(s))
60    }
61}
62
63/// Temporary struct to extend `Keccak256` with `io::Write`.
64struct Keccak256Ext(Keccak256);
65
66impl io::Write for Keccak256Ext {
67    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
68        self.0.update(buf);
69        Ok(buf.len())
70    }
71
72    fn flush(&mut self) -> io::Result<()> {
73        Ok(())
74    }
75}
76
77/// A vector of cryptographic hashes.
78/// This is used to represent a hash of a list of hashes.
79#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Hash, Serialize, Deserialize)]
80#[cfg_attr(with_testing, derive(Default))]
81pub struct CryptoHashVec(pub Vec<CryptoHash>);
82
83impl BcsHashable<'_> for CryptoHashVec {}
84
85impl Serialize for CryptoHash {
86    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
87    where
88        S: serde::ser::Serializer,
89    {
90        if serializer.is_human_readable() {
91            serializer.serialize_str(&self.to_string())
92        } else {
93            serializer.serialize_newtype_struct("CryptoHash", &self.as_bytes().0)
94        }
95    }
96}
97
98impl<'de> Deserialize<'de> for CryptoHash {
99    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
100    where
101        D: serde::de::Deserializer<'de>,
102    {
103        if deserializer.is_human_readable() {
104            let s = String::deserialize(deserializer)?;
105            let value = Self::from_str(&s).map_err(serde::de::Error::custom)?;
106            Ok(value)
107        } else {
108            #[derive(Deserialize)]
109            #[serde(rename = "CryptoHash")]
110            struct Foo([u8; 32]);
111
112            let value = Foo::deserialize(deserializer)?;
113            Ok(Self(value.0.into()))
114        }
115    }
116}
117
118impl FromStr for CryptoHash {
119    type Err = CryptoError;
120
121    fn from_str(s: &str) -> Result<Self, Self::Err> {
122        let value = hex::decode(s)?;
123        (value.as_slice()).try_into()
124    }
125}
126
127impl TryFrom<&[u8]> for CryptoHash {
128    type Error = CryptoError;
129
130    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
131        if value.len() != B256::len_bytes() {
132            return Err(CryptoError::IncorrectHashSize(value.len()));
133        }
134        Ok(Self(B256::from_slice(value)))
135    }
136}
137
138impl From<[u64; 4]> for CryptoHash {
139    fn from(integers: [u64; 4]) -> Self {
140        CryptoHash(crate::crypto::u64_array_to_be_bytes(integers).into())
141    }
142}
143
144impl From<CryptoHash> for [u64; 4] {
145    fn from(crypto_hash: CryptoHash) -> Self {
146        crate::crypto::be_bytes_to_u64_array(crypto_hash.0.as_ref())
147    }
148}
149
150impl fmt::Display for CryptoHash {
151    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
152        let prec = f.precision().unwrap_or(self.0.len() * 2);
153        hex::encode(&self.0[..prec.div_ceil(2)]).fmt(f)
154    }
155}
156
157impl fmt::Debug for CryptoHash {
158    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159        write!(f, "{}", hex::encode(&self.0[..8]))
160    }
161}
162
163impl WitType for CryptoHash {
164    const SIZE: u32 = <(u64, u64, u64, u64) as WitType>::SIZE;
165    type Layout = <(u64, u64, u64, u64) as WitType>::Layout;
166    type Dependencies = HList![];
167
168    fn wit_type_name() -> Cow<'static, str> {
169        "crypto-hash".into()
170    }
171
172    fn wit_type_declaration() -> Cow<'static, str> {
173        concat!(
174            "    record crypto-hash {\n",
175            "        part1: u64,\n",
176            "        part2: u64,\n",
177            "        part3: u64,\n",
178            "        part4: u64,\n",
179            "    }\n",
180        )
181        .into()
182    }
183}
184
185impl WitLoad for CryptoHash {
186    fn load<Instance>(
187        memory: &Memory<'_, Instance>,
188        location: GuestPointer,
189    ) -> Result<Self, RuntimeError>
190    where
191        Instance: InstanceWithMemory,
192        <Instance::Runtime as Runtime>::Memory: RuntimeMemory<Instance>,
193    {
194        let (part1, part2, part3, part4) = WitLoad::load(memory, location)?;
195        Ok(CryptoHash::from([part1, part2, part3, part4]))
196    }
197
198    fn lift_from<Instance>(
199        flat_layout: <Self::Layout as linera_witty::Layout>::Flat,
200        memory: &Memory<'_, Instance>,
201    ) -> Result<Self, RuntimeError>
202    where
203        Instance: InstanceWithMemory,
204        <Instance::Runtime as Runtime>::Memory: RuntimeMemory<Instance>,
205    {
206        let (part1, part2, part3, part4) = WitLoad::lift_from(flat_layout, memory)?;
207        Ok(CryptoHash::from([part1, part2, part3, part4]))
208    }
209}
210
211impl WitStore for CryptoHash {
212    fn store<Instance>(
213        &self,
214        memory: &mut Memory<'_, Instance>,
215        location: GuestPointer,
216    ) -> Result<(), RuntimeError>
217    where
218        Instance: InstanceWithMemory,
219        <Instance::Runtime as Runtime>::Memory: RuntimeMemory<Instance>,
220    {
221        let [part1, part2, part3, part4] = (*self).into();
222        (part1, part2, part3, part4).store(memory, location)
223    }
224
225    fn lower<Instance>(
226        &self,
227        memory: &mut Memory<'_, Instance>,
228    ) -> Result<<Self::Layout as Layout>::Flat, RuntimeError>
229    where
230        Instance: InstanceWithMemory,
231        <Instance::Runtime as Runtime>::Memory: RuntimeMemory<Instance>,
232    {
233        let [part1, part2, part3, part4] = (*self).into();
234        (part1, part2, part3, part4).lower(memory)
235    }
236}
237
238#[cfg(with_testing)]
239impl Arbitrary for CryptoHash {
240    type Parameters = ();
241    type Strategy = strategy::Map<VecStrategy<RangeInclusive<u8>>, fn(Vec<u8>) -> CryptoHash>;
242
243    fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
244        vec(u8::MIN..=u8::MAX, FixedBytes::<32>::len_bytes())
245            .prop_map(|vector| CryptoHash(B256::from_slice(&vector[..])))
246    }
247}
248
249doc_scalar!(CryptoHash, "A Keccak256 value");