Skip to main content

mur_common/
canonical.rs

1//! Canonical JSON serialization (sorted keys, no whitespace).
2//!
3//! Single source of truth used wherever two parties need to compute
4//! identical byte sequences over a JSON value:
5//!
6//! - identity rotation attestations (`mur_common::identity`)
7//! - card signing (`mur_common::card`)
8//! - MCP description hashing (B0 rule 6 / M9.2)
9//!
10//! The algorithm walks the `serde_json::Value` tree depth-first,
11//! sorts object keys lexicographically, and emits each leaf without
12//! whitespace. It does NOT attempt RFC 8785 (JCS) compliance — that
13//! would require number-canonicalisation rules we don't need for any
14//! current caller. Two callers using this helper round-trip
15//! losslessly; that's the only invariant we promise.
16
17/// Returns the canonical-JSON byte sequence for `value`. The output
18/// is deterministic across Rust runs, machine architectures, and
19/// `serde_json` versions (assuming the input `Value` is the same).
20pub fn canonical_json(value: &serde_json::Value) -> Vec<u8> {
21    let mut out = Vec::new();
22    write_canonical(&mut out, value);
23    out
24}
25
26/// Serialize `value` directly to canonical-JSON bytes without an
27/// intermediate `serde_json::Value`. For types that already implement
28/// `Serialize`, this avoids the round-trip allocation.
29pub fn canonical_json_for<T: serde::Serialize>(value: &T) -> Vec<u8> {
30    let v: serde_json::Value =
31        serde_json::to_value(value).expect("Serialize → Value should not fail for our types");
32    canonical_json(&v)
33}
34
35fn write_canonical(out: &mut Vec<u8>, v: &serde_json::Value) {
36    use serde_json::Value;
37    match v {
38        Value::Null => out.extend_from_slice(b"null"),
39        Value::Bool(b) => out.extend_from_slice(if *b { b"true" } else { b"false" }),
40        Value::Number(n) => out.extend_from_slice(n.to_string().as_bytes()),
41        Value::String(s) => {
42            let escaped = serde_json::to_string(s).expect("string serialization is infallible");
43            out.extend_from_slice(escaped.as_bytes());
44        }
45        Value::Array(arr) => {
46            out.push(b'[');
47            for (i, item) in arr.iter().enumerate() {
48                if i > 0 {
49                    out.push(b',');
50                }
51                write_canonical(out, item);
52            }
53            out.push(b']');
54        }
55        Value::Object(map) => {
56            // Sort keys lexicographically for deterministic output.
57            let mut keys: Vec<&String> = map.keys().collect();
58            keys.sort();
59            out.push(b'{');
60            for (i, k) in keys.iter().enumerate() {
61                if i > 0 {
62                    out.push(b',');
63                }
64                let kesc = serde_json::to_string(k).expect("string serialization is infallible");
65                out.extend_from_slice(kesc.as_bytes());
66                out.push(b':');
67                write_canonical(out, &map[*k]);
68            }
69            out.push(b'}');
70        }
71    }
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77    use serde_json::json;
78
79    #[test]
80    fn primitives_roundtrip() {
81        assert_eq!(canonical_json(&json!(null)), b"null");
82        assert_eq!(canonical_json(&json!(true)), b"true");
83        assert_eq!(canonical_json(&json!(false)), b"false");
84        assert_eq!(canonical_json(&json!(42)), b"42");
85        assert_eq!(canonical_json(&json!(0.5)), b"0.5");
86        assert_eq!(canonical_json(&json!("hi")), br#""hi""#);
87    }
88
89    #[test]
90    fn object_keys_are_sorted() {
91        let v = json!({"b": 1, "a": 2, "c": 3});
92        assert_eq!(canonical_json(&v), br#"{"a":2,"b":1,"c":3}"#);
93    }
94
95    #[test]
96    fn nested_objects_recursively_sorted() {
97        let v = json!({"x": {"b": 1, "a": 2}, "a": {"y": 1, "x": 2}});
98        let want = br#"{"a":{"x":2,"y":1},"x":{"a":2,"b":1}}"#;
99        assert_eq!(canonical_json(&v), want);
100    }
101
102    #[test]
103    fn arrays_preserve_order() {
104        let v = json!([3, 1, 2]);
105        assert_eq!(canonical_json(&v), b"[3,1,2]");
106    }
107
108    #[test]
109    fn whitespace_stripped() {
110        // serde_json::Value normalises away source whitespace, but
111        // make sure our writer doesn't introduce any either.
112        let v: serde_json::Value =
113            serde_json::from_str(r#" { "a" : 1 ,  "b" : [ 2 , 3 ] } "#).unwrap();
114        assert_eq!(canonical_json(&v), br#"{"a":1,"b":[2,3]}"#);
115    }
116
117    #[test]
118    fn key_order_is_independent_of_insertion_order() {
119        let a: serde_json::Value = serde_json::from_str(r#"{"a":1,"b":2}"#).unwrap();
120        let b: serde_json::Value = serde_json::from_str(r#"{"b":2,"a":1}"#).unwrap();
121        assert_eq!(canonical_json(&a), canonical_json(&b));
122    }
123
124    #[test]
125    fn canonical_json_for_typed_value() {
126        #[derive(serde::Serialize)]
127        struct Probe {
128            tools: Vec<&'static str>,
129            version: u32,
130        }
131        let p = Probe {
132            tools: vec!["a", "b"],
133            version: 1,
134        };
135        // Field order of the input struct must not affect the output —
136        // the keys are sorted.
137        assert_eq!(
138            canonical_json_for(&p),
139            br#"{"tools":["a","b"],"version":1}"#
140        );
141    }
142}