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#[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 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 pub fn to_hex(self) -> String {
37 self.to_string()
38 }
39
40 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
68impl 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
82impl<'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 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 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 let checksum = "722c8c993fd75a7627d69ed941344fe2a1423a3e75efd3e6778a142884227104";
179 let parsed = Checksum::from_hex(checksum).unwrap();
180 assert_eq!(parsed, Checksum::generate(b"hij"));
181 assert_eq!(parsed.to_hex(), checksum);
183
184 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 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 let _: &[u8; 32] = checksum.as_ref();
216 let _: &[u8] = checksum.as_ref();
217 let _: &[u8; 32] = checksum.as_ref();
219 let _: &[u8] = checksum.as_ref();
220 }
221
222 #[test]
223 fn serde_works() {
224 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}