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