wasm_hash_verifier/
xand_hash.rs

1use crate::xand_hash::v1::V1XandHash;
2use alloc::vec::Vec;
3use postcard::{from_bytes, to_allocvec};
4use serde::{Deserialize, Deserializer, Serialize, Serializer};
5
6pub mod v1;
7
8/// Standard target type for `WasmBlob` hashing.
9/// Versioning allows future hashed types to be added while not breaking existing code.
10#[non_exhaustive]
11#[derive(Clone, Debug, Eq, Hash, PartialEq)]
12pub enum XandHash {
13    V1(V1XandHash),
14}
15
16// **WARNING: Don't make public** There is panicking code in the `From` impls for `XandHash` that
17// operate under the assumption that `SerdeXandHash` can only be constructed via `From`.
18#[derive(Serialize, Deserialize)]
19struct SerdeXandHash {
20    variant: u8,
21    inner: Vec<u8>,
22}
23
24#[allow(clippy::fallible_impl_from)]
25impl From<SerdeXandHash> for XandHash {
26    fn from(serde_xand_hash: SerdeXandHash) -> Self {
27        match serde_xand_hash.variant {
28            1 => {
29                let contents = from_bytes(&serde_xand_hash.inner).expect("Should never fail: see prop tests");
30                Self::V1(contents)
31            }
32            _ => unimplemented!("Invalid SerdeXandHash: no variant {:?}", serde_xand_hash.variant),
33        }
34    }
35}
36
37#[allow(unreachable_patterns, clippy::fallible_impl_from)]
38impl From<&XandHash> for SerdeXandHash {
39    fn from(xand_hash: &XandHash) -> Self {
40        match xand_hash {
41            XandHash::V1(contents) => {
42                let variant = 1;
43                let inner = to_allocvec(&contents).expect("Should never fail: see prop tests");
44                Self { variant, inner }
45            }
46            _ => unreachable!("Add new variants here as needed"),
47        }
48    }
49}
50
51impl Serialize for XandHash {
52    fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
53    where
54        S: Serializer,
55    {
56        let serde_xand_hash: SerdeXandHash = self.into();
57        serde_xand_hash.serialize(serializer)
58    }
59}
60
61impl<'de> Deserialize<'de> for XandHash {
62    fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error>
63    where
64        D: Deserializer<'de>,
65    {
66        let serde_hash: SerdeXandHash = Deserialize::deserialize(deserializer)?;
67        Ok(serde_hash.into())
68    }
69}
70
71#[cfg(test)]
72impl XandHash {
73    #[must_use]
74    pub const fn new(inner: blake3::Hash) -> Self {
75        Self::V1(V1XandHash::new(inner))
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82    use proptest::prelude::*;
83
84    prop_compose! {
85        fn arbitrary_xand_hash()(
86            bytes: [u8;32]
87        ) -> XandHash {
88            let blake3_hash = bytes.into();
89            XandHash::new(blake3_hash)
90        }
91    }
92
93    proptest! {
94        #[test]
95        fn v1__round_trip_serialization_deserialization(
96            expected in arbitrary_xand_hash()
97        ) {
98            let serialized = bincode::serialize(&expected).unwrap();
99            let actual: XandHash = bincode::deserialize(&serialized).unwrap();
100            assert_eq!(expected, actual);
101        }
102    }
103}