cosmwasm_std/
checksum.rs

1use core::fmt;
2
3use schemars::JsonSchema;
4use serde::{de, ser, Deserialize, Deserializer, Serialize};
5use sha2::{Digest, Sha256};
6use thiserror::Error;
7
8use crate::prelude::*;
9use crate::{StdError, StdResult};
10
11/// A SHA-256 checksum of a Wasm blob, used to identify a Wasm code.
12/// This must remain stable since this checksum is stored in the blockchain state.
13///
14/// This is often referred to as "code ID" in go-cosmwasm, even if code ID
15/// usually refers to an auto-incrementing number.
16#[derive(JsonSchema, Debug, Copy, Clone, PartialEq, Eq, Hash)]
17pub struct Checksum(#[schemars(with = "String")] [u8; 32]);
18
19impl Checksum {
20    pub fn generate(wasm: &[u8]) -> Self {
21        Checksum(Sha256::digest(wasm).into())
22    }
23
24    /// Tries to parse the given hex string into a checksum.
25    /// Errors if the string contains non-hex characters or does not contain 32 bytes.
26    pub fn from_hex(input: &str) -> StdResult<Self> {
27        let mut binary = [0u8; 32];
28        hex::decode_to_slice(input, &mut binary).map_err(StdError::invalid_hex)?;
29
30        Ok(Self(binary))
31    }
32
33    /// Creates a lowercase hex encoded copy of this checksum.
34    ///
35    /// This takes an owned `self` instead of a reference because `Checksum` is cheap to `Copy`.
36    pub fn to_hex(self) -> String {
37        self.to_string()
38    }
39
40    /// Returns a reference to the inner bytes of this checksum as a slice.
41    /// If you need a reference to the array, use [`AsRef::as_ref`].
42    pub fn as_slice(&self) -> &[u8] {
43        &self.0
44    }
45}
46
47impl fmt::Display for Checksum {
48    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
49        for byte in self.0.iter() {
50            write!(f, "{byte:02x}")?;
51        }
52        Ok(())
53    }
54}
55
56impl From<[u8; 32]> for Checksum {
57    fn from(data: [u8; 32]) -> Self {
58        Checksum(data)
59    }
60}
61
62impl AsRef<[u8; 32]> for Checksum {
63    fn as_ref(&self) -> &[u8; 32] {
64        &self.0
65    }
66}
67
68/// Serializes as a hex string
69impl Serialize for Checksum {
70    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
71    where
72        S: ser::Serializer,
73    {
74        if serializer.is_human_readable() {
75            serializer.serialize_str(&self.to_hex())
76        } else {
77            panic!("Checksum is only intended to be used with JSON serialization for now. If you are hitting this panic please open an issue at https://github.com/CosmWasm/cosmwasm describing your use case.")
78        }
79    }
80}
81
82/// Deserializes as a hex string
83impl<'de> Deserialize<'de> for Checksum {
84    fn deserialize<D>(deserializer: D) -> Result<Checksum, D::Error>
85    where
86        D: Deserializer<'de>,
87    {
88        if deserializer.is_human_readable() {
89            deserializer.deserialize_str(ChecksumVisitor)
90        } else {
91            panic!("Checksum is only intended to be used with JSON serialization for now. If you are hitting this panic please open an issue at https://github.com/CosmWasm/cosmwasm describing your use case.")
92        }
93    }
94}
95
96struct ChecksumVisitor;
97
98impl<'de> de::Visitor<'de> for ChecksumVisitor {
99    type Value = Checksum;
100
101    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
102        formatter.write_str("valid hex encoded 32 byte checksum")
103    }
104
105    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
106    where
107        E: de::Error,
108    {
109        match Checksum::from_hex(v) {
110            Ok(data) => Ok(data),
111            Err(_) => Err(E::custom(format!("invalid checksum: {v}"))),
112        }
113    }
114}
115
116#[derive(Error, Debug)]
117#[error("Checksum not of length 32")]
118pub struct ChecksumError;
119
120impl TryFrom<&[u8]> for Checksum {
121    type Error = ChecksumError;
122
123    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
124        if value.len() != 32 {
125            return Err(ChecksumError);
126        }
127        let mut data = [0u8; 32];
128        data.copy_from_slice(value);
129        Ok(Checksum(data))
130    }
131}
132
133impl From<Checksum> for Vec<u8> {
134    fn from(original: Checksum) -> Vec<u8> {
135        original.0.into()
136    }
137}
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142
143    use crate::to_json_string;
144
145    #[test]
146    fn generate_works() {
147        let wasm = vec![0x68, 0x69, 0x6a];
148        let checksum = Checksum::generate(&wasm);
149
150        // echo -n "hij" | sha256sum
151        let expected = [
152            0x72, 0x2c, 0x8c, 0x99, 0x3f, 0xd7, 0x5a, 0x76, 0x27, 0xd6, 0x9e, 0xd9, 0x41, 0x34,
153            0x4f, 0xe2, 0xa1, 0x42, 0x3a, 0x3e, 0x75, 0xef, 0xd3, 0xe6, 0x77, 0x8a, 0x14, 0x28,
154            0x84, 0x22, 0x71, 0x04,
155        ];
156        assert_eq!(checksum.0, expected);
157    }
158
159    #[test]
160    fn implemented_display() {
161        let wasm = vec![0x68, 0x69, 0x6a];
162        let checksum = Checksum::generate(&wasm);
163        // echo -n "hij" | sha256sum
164        let embedded = format!("Check: {checksum}");
165        assert_eq!(
166            embedded,
167            "Check: 722c8c993fd75a7627d69ed941344fe2a1423a3e75efd3e6778a142884227104"
168        );
169        assert_eq!(
170            checksum.to_string(),
171            "722c8c993fd75a7627d69ed941344fe2a1423a3e75efd3e6778a142884227104"
172        );
173    }
174
175    #[test]
176    fn from_hex_works() {
177        // echo -n "hij" | sha256sum
178        let checksum = "722c8c993fd75a7627d69ed941344fe2a1423a3e75efd3e6778a142884227104";
179        let parsed = Checksum::from_hex(checksum).unwrap();
180        assert_eq!(parsed, Checksum::generate(b"hij"));
181        // should be inverse of `to_hex`
182        assert_eq!(parsed.to_hex(), checksum);
183
184        // invalid hex
185        let too_short = "722c8c993fd75a7627d69ed941344fe2a1423a3e75efd3e6778a1428842271";
186        assert!(Checksum::from_hex(too_short).is_err());
187        let invalid_char = "722c8c993fd75a7627d69ed941344fe2a1423a3e75efd3e6778a1428842271g4";
188        assert!(Checksum::from_hex(invalid_char).is_err());
189        let too_long = "722c8c993fd75a7627d69ed941344fe2a1423a3e75efd3e6778a14288422710400";
190        assert!(Checksum::from_hex(too_long).is_err());
191    }
192
193    #[test]
194    fn to_hex_works() {
195        let wasm = vec![0x68, 0x69, 0x6a];
196        let checksum = Checksum::generate(&wasm);
197        // echo -n "hij" | sha256sum
198        assert_eq!(
199            checksum.to_hex(),
200            "722c8c993fd75a7627d69ed941344fe2a1423a3e75efd3e6778a142884227104"
201        );
202    }
203
204    #[test]
205    fn into_vec_works() {
206        let checksum = Checksum::generate(&[12u8; 17]);
207        let as_vec: Vec<u8> = checksum.into();
208        assert_eq!(as_vec, checksum.0);
209    }
210
211    #[test]
212    fn ref_conversions_work() {
213        let checksum = Checksum::generate(&[12u8; 17]);
214        // as_ref
215        let _: &[u8; 32] = checksum.as_ref();
216        let _: &[u8] = checksum.as_ref();
217        // as_slice
218        let _: &[u8; 32] = checksum.as_ref();
219        let _: &[u8] = checksum.as_ref();
220    }
221
222    #[test]
223    fn serde_works() {
224        // echo -n "hij" | sha256sum
225        let checksum =
226            Checksum::from_hex("722c8c993fd75a7627d69ed941344fe2a1423a3e75efd3e6778a142884227104")
227                .unwrap();
228
229        let serialized = to_json_string(&checksum).unwrap();
230        assert_eq!(
231            serialized,
232            "\"722c8c993fd75a7627d69ed941344fe2a1423a3e75efd3e6778a142884227104\""
233        );
234
235        let deserialized: Checksum = serde_json::from_str(&serialized).unwrap();
236        assert_eq!(deserialized, checksum);
237    }
238}