agent_scroll/
canonical.rs1use serde_json::{Number, Value};
2use sha2::{Digest, Sha256};
3
4use crate::error::Error;
5
6fn normalize_numbers(value: Value) -> Value {
10 const SAFE: u64 = 1u64 << 53;
11 match value {
12 Value::Object(map) => Value::Object(
13 map.into_iter()
14 .map(|(k, v)| (k, normalize_numbers(v)))
15 .collect(),
16 ),
17 Value::Array(arr) => Value::Array(arr.into_iter().map(normalize_numbers).collect()),
18 Value::Number(n) => {
19 if let Some(u) = n.as_u64() {
20 if u > SAFE {
21 return Number::from_f64(u as f64)
22 .map(Value::Number)
23 .unwrap_or(Value::Number(n));
24 }
25 } else if let Some(i) = n.as_i64() {
26 if i.unsigned_abs() > SAFE {
27 return Number::from_f64(i as f64)
28 .map(Value::Number)
29 .unwrap_or(Value::Number(n));
30 }
31 }
32 Value::Number(n)
33 }
34 other => other,
35 }
36}
37
38pub fn canonical(value: &Value) -> Result<Vec<u8>, Error> {
39 let normalized = normalize_numbers(value.clone());
40 serde_jcs::to_string(&normalized)
41 .map(|s| s.into_bytes())
42 .map_err(|e| Error::Invalid(format!("jcs: {e}")))
43}
44
45pub fn hash_canonical(value: &Value) -> Result<String, Error> {
46 let bytes = canonical(value)?;
47 let digest = Sha256::digest(&bytes);
48 Ok(format!("sha256:{}", hex_lower(&digest)))
49}
50
51fn hex_lower(bytes: &[u8]) -> String {
52 let mut s = String::with_capacity(bytes.len() * 2);
53 for b in bytes {
54 s.push_str(&format!("{:02x}", b));
55 }
56 s
57}