Skip to main content

appletheia_application/command/
default_command_hasher.rs

1use std::fmt::Write;
2
3use sha2::{Digest, Sha256};
4
5use crate::command::{Command, CommandHash, CommandHasher, CommandHasherError};
6
7#[derive(Clone, Copy, Debug, Default)]
8pub struct DefaultCommandHasher;
9
10impl DefaultCommandHasher {
11    pub fn new() -> Self {
12        Self
13    }
14
15    fn canonicalize_json(value: serde_json::Value) -> serde_json::Value {
16        match value {
17            serde_json::Value::Array(array) => serde_json::Value::Array(
18                array
19                    .into_iter()
20                    .map(Self::canonicalize_json)
21                    .collect::<Vec<_>>(),
22            ),
23            serde_json::Value::Object(map) => {
24                let mut entries: Vec<(String, serde_json::Value)> = map.into_iter().collect();
25                entries.sort_by(|(a, _), (b, _)| a.cmp(b));
26
27                let mut sorted = serde_json::Map::with_capacity(entries.len());
28                for (key, value) in entries {
29                    sorted.insert(key, Self::canonicalize_json(value));
30                }
31                serde_json::Value::Object(sorted)
32            }
33            other => other,
34        }
35    }
36
37    fn to_lower_hex(bytes: &[u8]) -> String {
38        let mut out = String::with_capacity(bytes.len() * 2);
39        for &b in bytes {
40            let _ = write!(&mut out, "{:02x}", b);
41        }
42        out
43    }
44}
45
46impl CommandHasher for DefaultCommandHasher {
47    fn command_hash<C: Command>(&self, command: &C) -> Result<CommandHash, CommandHasherError> {
48        let value = serde_json::to_value(command)?;
49        let canonical = Self::canonicalize_json(value);
50        let json = serde_json::to_string(&canonical)?;
51
52        let mut hasher = Sha256::new();
53        hasher.update(json.as_bytes());
54        let hash = Self::to_lower_hex(&hasher.finalize());
55        Ok(CommandHash::new(hash)?)
56    }
57}