canic_core/cdk/utils/
hash.rs1use sha2::{Digest, Sha256};
8use std::{error::Error, fmt};
9
10pub type HashBytes = Vec<u8>;
17
18#[must_use]
20pub fn sha256_bytes(bytes: &[u8]) -> HashBytes {
21 let mut hasher = Sha256::new();
22 hasher.update(bytes);
23 hasher.finalize().to_vec()
24}
25
26#[must_use]
28pub fn sha256_hex(bytes: &[u8]) -> String {
29 hex_bytes(sha256_bytes(bytes))
30}
31
32#[must_use]
34pub fn wasm_hash(bytes: &[u8]) -> HashBytes {
35 sha256_bytes(bytes)
36}
37
38#[must_use]
40pub fn wasm_hash_hex(bytes: &[u8]) -> String {
41 sha256_hex(bytes)
42}
43
44#[must_use]
46pub fn hex_bytes(bytes: impl AsRef<[u8]>) -> String {
47 let bytes = bytes.as_ref();
48 let mut encoded = String::with_capacity(bytes.len() * 2);
49
50 for byte in bytes {
51 encoded.push(hex_char(byte >> 4));
52 encoded.push(hex_char(byte & 0x0f));
53 }
54
55 encoded
56}
57
58pub fn decode_hex(hex: &str) -> Result<HashBytes, DecodeHexError> {
60 if !hex.len().is_multiple_of(2) {
61 return Err(DecodeHexError::OddLength(hex.len()));
62 }
63
64 let mut bytes = Vec::with_capacity(hex.len() / 2);
65 for index in (0..hex.len()).step_by(2) {
66 let high = decode_nibble(hex.as_bytes()[index], index)?;
67 let low = decode_nibble(hex.as_bytes()[index + 1], index + 1)?;
68 bytes.push((high << 4) | low);
69 }
70
71 Ok(bytes)
72}
73
74fn hex_char(nibble: u8) -> char {
76 char::from(b"0123456789abcdef"[usize::from(nibble & 0x0f)])
77}
78
79fn decode_nibble(byte: u8, index: usize) -> Result<u8, DecodeHexError> {
81 match byte {
82 b'0'..=b'9' => Ok(byte - b'0'),
83 b'a'..=b'f' => Ok(byte - b'a' + 10),
84 b'A'..=b'F' => Ok(byte - b'A' + 10),
85 _ => Err(DecodeHexError::InvalidDigit {
86 index,
87 byte: char::from(byte),
88 }),
89 }
90}
91
92#[derive(Clone, Debug, Eq, PartialEq)]
99pub enum DecodeHexError {
100 OddLength(usize),
102
103 InvalidDigit { index: usize, byte: char },
105}
106
107impl fmt::Display for DecodeHexError {
108 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
109 match self {
110 Self::OddLength(length) => {
111 write!(formatter, "hex string must have even length, got {length}")
112 }
113 Self::InvalidDigit { index, byte } => {
114 write!(formatter, "invalid hex digit {byte:?} at index {index}")
115 }
116 }
117 }
118}
119
120impl Error for DecodeHexError {}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125
126 const EMPTY_SHA256: &str = "e3b0c44298fc1c149afbf4c8996fb924\
127 27ae41e4649b934ca495991b7852b855";
128
129 #[test]
130 fn wasm_hash_hex_matches_sha256_vector() {
131 assert_eq!(wasm_hash_hex(&[]), EMPTY_SHA256);
132 }
133
134 #[test]
135 fn hex_round_trip_accepts_upper_and_lowercase() {
136 assert_eq!(decode_hex("01aBff").expect("decode hex"), vec![1, 171, 255]);
137 assert_eq!(hex_bytes([1, 171, 255]), "01abff");
138 }
139
140 #[test]
141 fn decode_hex_rejects_invalid_input() {
142 std::assert_matches!(decode_hex("f"), Err(DecodeHexError::OddLength(1)));
143 std::assert_matches!(
144 decode_hex("0g"),
145 Err(DecodeHexError::InvalidDigit { index: 1, .. })
146 );
147 }
148}