//! Deterministic JSON encoding for the payload-signing contract. //! //! Rule: serialize a payload into `serde_json::Value`, then write that //! value to a string. The default `serde_json::Map` is `BTreeMap` (the //! `preserve_order` feature is *not* enabled here), so `to_value` //! produces sorted keys at every level. `to_string` emits compact JSON //! with no insignificant whitespace and no trailing newline. //! //! The writer and the verifier both call `canonical_json`. Two //! `serde_json` invocations are not free, but the payloads are small //! (single-KB), and using `serde_json` directly keeps the canonical //! rule a function of a widely-audited dependency rather than a //! hand-rolled encoder. //! //! `serde_json::Value` represents numbers via its own `Number` type, //! preserving the input formatting (integers stay integral, floats stay //! float). Floats are written by Rust's `f64::to_string`, which is //! bit-stable across Rust versions in stable releases. use serde::Serialize; #[derive(Debug)] pub enum CanonicalError { Serde(serde_json::Error), } impl core::fmt::Display for CanonicalError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { Self::Serde(e) => write!(f, "canonical encoding failed: {e}"), } } } impl std::error::Error for CanonicalError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { Self::Serde(e) => Some(e), } } } impl From for CanonicalError { fn from(e: serde_json::Error) -> Self { Self::Serde(e) } } /// Encode `payload` as canonical JSON suitable for signing. /// /// See the module doc for the encoding rule. The output is the exact /// byte string a verifier will re-derive from the same `payload`. pub fn canonical_json(payload: &T) -> Result { let value = serde_json::to_value(payload)?; Ok(serde_json::to_string(&value)?) }