Skip to main content

max / everycycle

2.0 KB · 57 lines History Blame Raw
1 //! Deterministic JSON encoding for the payload-signing contract.
2 //!
3 //! Rule: serialize a payload into `serde_json::Value`, then write that
4 //! value to a string. The default `serde_json::Map` is `BTreeMap` (the
5 //! `preserve_order` feature is *not* enabled here), so `to_value`
6 //! produces sorted keys at every level. `to_string` emits compact JSON
7 //! with no insignificant whitespace and no trailing newline.
8 //!
9 //! The writer and the verifier both call `canonical_json`. Two
10 //! `serde_json` invocations are not free, but the payloads are small
11 //! (single-KB), and using `serde_json` directly keeps the canonical
12 //! rule a function of a widely-audited dependency rather than a
13 //! hand-rolled encoder.
14 //!
15 //! `serde_json::Value` represents numbers via its own `Number` type,
16 //! preserving the input formatting (integers stay integral, floats stay
17 //! float). Floats are written by Rust's `f64::to_string`, which is
18 //! bit-stable across Rust versions in stable releases.
19
20 use serde::Serialize;
21
22 #[derive(Debug)]
23 pub enum CanonicalError {
24 Serde(serde_json::Error),
25 }
26
27 impl core::fmt::Display for CanonicalError {
28 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
29 match self {
30 Self::Serde(e) => write!(f, "canonical encoding failed: {e}"),
31 }
32 }
33 }
34
35 impl std::error::Error for CanonicalError {
36 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
37 match self {
38 Self::Serde(e) => Some(e),
39 }
40 }
41 }
42
43 impl From<serde_json::Error> for CanonicalError {
44 fn from(e: serde_json::Error) -> Self {
45 Self::Serde(e)
46 }
47 }
48
49 /// Encode `payload` as canonical JSON suitable for signing.
50 ///
51 /// See the module doc for the encoding rule. The output is the exact
52 /// byte string a verifier will re-derive from the same `payload`.
53 pub fn canonical_json<T: Serialize>(payload: &T) -> Result<String, CanonicalError> {
54 let value = serde_json::to_value(payload)?;
55 Ok(serde_json::to_string(&value)?)
56 }
57