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