proofborne_core/
canonical.rs1use serde_json::Value;
2
3pub fn canonical_json_bytes(value: &Value) -> Vec<u8> {
8 let mut output = Vec::new();
9 write_value(value, &mut output);
10 output
11}
12
13fn write_value(value: &Value, output: &mut Vec<u8>) {
14 match value {
15 Value::Null => output.extend_from_slice(b"null"),
16 Value::Bool(value) => output.extend_from_slice(if *value { b"true" } else { b"false" }),
17 Value::Number(value) => output.extend_from_slice(value.to_string().as_bytes()),
18 Value::String(value) => {
19 let encoded =
20 serde_json::to_string(value).expect("serializing a JSON string cannot fail");
21 output.extend_from_slice(encoded.as_bytes());
22 }
23 Value::Array(values) => {
24 output.push(b'[');
25 for (index, value) in values.iter().enumerate() {
26 if index > 0 {
27 output.push(b',');
28 }
29 write_value(value, output);
30 }
31 output.push(b']');
32 }
33 Value::Object(values) => {
34 output.push(b'{');
35 let mut keys: Vec<_> = values.keys().collect();
36 keys.sort_unstable();
37 for (index, key) in keys.iter().enumerate() {
38 if index > 0 {
39 output.push(b',');
40 }
41 let encoded =
42 serde_json::to_string(key).expect("serializing a JSON key cannot fail");
43 output.extend_from_slice(encoded.as_bytes());
44 output.push(b':');
45 write_value(&values[*key], output);
46 }
47 output.push(b'}');
48 }
49 }
50}
51
52pub fn hash_bytes(bytes: impl AsRef<[u8]>) -> String {
54 blake3::hash(bytes.as_ref()).to_hex().to_string()
55}
56
57pub fn hash_json(value: &Value) -> String {
59 hash_bytes(canonical_json_bytes(value))
60}
61
62#[cfg(test)]
63mod tests {
64 use serde_json::json;
65
66 use super::*;
67
68 #[test]
69 fn object_key_order_does_not_change_hash() {
70 let left = json!({"z": 1, "a": {"two": 2, "one": 1}});
71 let right = json!({"a": {"one": 1, "two": 2}, "z": 1});
72 assert_eq!(canonical_json_bytes(&left), canonical_json_bytes(&right));
73 assert_eq!(hash_json(&left), hash_json(&right));
74 }
75
76 #[test]
77 fn array_order_changes_hash() {
78 assert_ne!(hash_json(&json!([1, 2])), hash_json(&json!([2, 1])));
79 }
80}