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<CryptoHash> for [u8; 32] {
139    fn from(crypto_hash: CryptoHash) -> Self {
140        crypto_hash.0 .0
141    }
142}
143
144impl From<[u64; 4]> for CryptoHash {
145    fn from(integers: [u64; 4]) -> Self {
146        CryptoHash(crate::crypto::u64_array_to_be_bytes(integers).into())
147    }
148}
149
150impl From<CryptoHash> for [u64; 4] {
151    fn from(crypto_hash: CryptoHash) -> Self {
152        crate::crypto::be_bytes_to_u64_array(crypto_hash.0.as_ref())
153    }
154}
155
156impl fmt::Display for CryptoHash {
157    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158        let prec = f.precision().unwrap_or(self.0.len() * 2);
159        hex::encode(&self.0[..prec.div_ceil(2)]).fmt(f)
160    }
161}
162
163impl fmt::Debug for CryptoHash {
164    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165        write!(f, "{}", hex::encode(&self.0[..8]))
166    }
167}
168
169impl WitType for CryptoHash {
170    const SIZE: u32 = <(u64, u64, u64, u64) as WitType>::SIZE;
171    type Layout = <(u64, u64, u64, u64) as WitType>::Layout;
172    type Dependencies = HList![];
173
174    fn wit_type_name() -> Cow<'static, str> {
175        "crypto-hash".into()
176    }
177
178    fn wit_type_declaration() -> Cow<'static, str> {
179        concat!(
180            "    record crypto-hash {\n",
181            "        part1: u64,\n",
182            "        part2: u64,\n",
183            "        part3: u64,\n",
184            "        part4: u64,\n",
185            "    }\n",
186        )
187        .into()
188    }
189}
190
191impl WitLoad for CryptoHash {
192    fn load<Instance>(
193        memory: &Memory<'_, Instance>,
194        location: GuestPointer,
195    ) -> Result<Self, RuntimeError>
196    where
197        Instance: InstanceWithMemory,
198        <Instance::Runtime as Runtime>::Memory: RuntimeMemory<Instance>,
199    {
200        let (part1, part2, part3, part4) = WitLoad::load(memory, location)?;
201        Ok(CryptoHash::from([part1, part2, part3, part4]))
202    }
203
204    fn lift_from<Instance>(
205        flat_layout: <Self::Layout as linera_witty::Layout>::Flat,
206        memory: &Memory<'_, Instance>,
207    ) -> Result<Self, RuntimeError>
208    where
209        Instance: InstanceWithMemory,
210        <Instance::Runtime as Runtime>::Memory: RuntimeMemory<Instance>,
211    {
212        let (part1, part2, part3, part4) = WitLoad::lift_from(flat_layout, memory)?;
213        Ok(CryptoHash::from([part1, part2, part3, part4]))
214    }
215}
216
217impl WitStore for CryptoHash {
218    fn store<Instance>(
219        &self,
220        memory: &mut Memory<'_, Instance>,
221        location: GuestPointer,
222    ) -> Result<(), RuntimeError>
223    where
224        Instance: InstanceWithMemory,
225        <Instance::Runtime as Runtime>::Memory: RuntimeMemory<Instance>,
226    {
227        let [part1, part2, part3, part4] = (*self).into();
228        (part1, part2, part3, part4).store(memory, location)
229    }
230
231    fn lower<Instance>(
232        &self,
233        memory: &mut Memory<'_, Instance>,
234    ) -> Result<<Self::Layout as Layout>::Flat, RuntimeError>
235    where
236        Instance: InstanceWithMemory,
237        <Instance::Runtime as Runtime>::Memory: RuntimeMemory<Instance>,
238    {
239        let [part1, part2, part3, part4] = (*self).into();
240        (part1, part2, part3, part4).lower(memory)
241    }
242}
243
244#[cfg(with_testing)]
245impl Arbitrary for CryptoHash {
246    type Parameters = ();
247    type Strategy = strategy::Map<VecStrategy<RangeInclusive<u8>>, fn(Vec<u8>) -> CryptoHash>;
248
249    fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
250        vec(u8::MIN..=u8::MAX, FixedBytes::<32>::len_bytes())
251            .prop_map(|vector| CryptoHash(B256::from_slice(&vector[..])))
252    }
253}
254
255doc_scalar!(CryptoHash, "A Keccak256 value");