1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
//! # vr-jcs
//!
//! RFC 8785 JSON Canonicalization Scheme (JCS) for Rust.
//!
//! Produces canonical JSON suitable for deterministic digest computation,
//! content hashing, and stable serialization boundaries. Implements the
//! RFC 8785 rules that materially affect wire compatibility:
//! - UTF-16 code-unit sorting for object property names
//! - ECMAScript-compatible primitive serialization
//! - UTF-8 output without insignificant whitespace
//! - duplicate-property rejection on raw JSON parse paths
//! - I-JSON string / number validation
//!
//! ## Module layout
//!
//! - [`canonicalize`] — RFC 8785 emit + in-place key sorting (also
//! re-exports the public [`canonicalize()`](crate::canonicalize) function).
//! - [`strict_parse`] — strict admission parser for untrusted JSON
//! (duplicate-key rejection, I-JSON validation, depth limit).
//! - `digest` — strategy-bearing canonical-bytes digest API.
//! - `canonical_bytes` — [`CanonicalBytes`] newtype boundary.
//! - `number` — internal ECMAScript number rendering (RFC 8785 §3.2.6).
//! - [`error`](crate) — [`JcsError`] / [`JcsErrorInfo`].
//!
//! ## API
//!
//! ### Strict path (for untrusted JSON)
//!
//! - [`to_canon_bytes_from_slice`] — Parse untrusted JSON, apply strict admission checks, emit canonical bytes
//! - [`to_canon_string_from_str`] — Parse untrusted JSON string, apply strict admission checks, emit canonical string
//!
//! ### Typed path (caller-controlled construction only, deprecated)
//!
//! - [`to_canon_bytes`] — Serialize any `Serialize` type to canonical JSON bytes
//! - [`to_canon_string`] — Serialize any `Serialize` type to a canonical JSON string
//!
//! ### In-place
//!
//! - [`canonicalize()`](crate::canonicalize) — Sort object keys recursively in a `serde_json::Value`
//! and validate I-JSON strings + numbers.
//!
//! ### Canonical digest
//!
//! Canonicalization is a schema decision; digest algorithm choice (BLAKE3 vs.
//! keyed BLAKE3 vs. domain-separated BLAKE3 vs. SHA-256 vs. …) is a separate
//! cryptographic / governance decision. The digest surface reflects that split:
//!
//! **Strategy-bearing (primary) path** — for any site whose digest algorithm
//! is or may become a policy variable:
//!
//! - [`DigestAlgorithm`] — the algorithm enum.
//! - [`DigestStrategy`] — an algorithm plus any future policy knobs.
//! - [`CanonicalDigest`] — typed output that remembers which algorithm produced it.
//! - [`to_canon_digest_with`] — canonicalize `value`, digest under `strategy`.
//!
//! **BLAKE3 fixed-policy convenience** — for sites where receipt policy has
//! explicitly frozen the algorithm to plain BLAKE3:
//!
//! - [`to_canon_blake3_digest`] — `&Value` → `[u8; 32]`.
//! - [`to_canon_blake3_digest_from_slice`] — strict-parse `&[u8]` → `[u8; 32]`.
//!
//! These convenience wrappers are equivalent to calling
//! [`to_canon_digest_with`] with [`DigestStrategy::blake3_untagged`] and
//! extracting `bytes`.
//!
//! The lexical invariant is: canonicalization and digest must travel together
//! through one call. Receipt-bound and constitutional code paths MUST use the
//! strategy-bearing or fixed-BLAKE3 wrappers instead of pairing
//! `to_canon_bytes_*` with `blake3::hash` manually.
//!
//! ## Usage
//!
//! ```
//! # fn main() -> Result<(), vr_jcs::JcsError> {
//! let json = vr_jcs::to_canon_string_from_str(r#"{"z_field":1,"a_field":2}"#)?;
//! assert_eq!(json, r#"{"a_field":2,"z_field":1}"#);
//! # Ok(())
//! # }
//! ```
use Serialize;
/// Maximum permitted nesting depth for JSON structures (128).
pub const MAX_NESTING_DEPTH: usize = 128;
pub use CanonicalBytes;
pub use canonicalize;
pub use ;
pub use ;
// Backward-compatible top-level re-exports of strict_parse helpers.
// `vertrule-schemas` and any other sibling-crate consumer reaches these
// through `vr_jcs::*`; the module path `vr_jcs::strict_parse::*` is the
// preferred location for new code.
pub use ;
// Crate-private re-export so `lib_tests.rs` (an internal test module
// brought in via `#[path = "lib_tests.rs"] mod tests`) can keep its
// `super::*` import shape after the canonicalize-module split.
pub use to_canon_bytes_value;
// ── Public API ─────────────────────────────────────────────────────
/// Serialize any `Serialize` type to canonical JSON bytes.
///
/// The typed `Serialize` path is not authoritative for untrusted raw JSON
/// because it does not control parse-time object-member admission. For
/// untrusted input, use [`to_canon_bytes_from_slice`] instead.
///
/// # Errors
///
/// Returns:
/// - [`JcsError::Json`] if serialization to JSON fails
/// - [`JcsError::InvalidString`] if a string contains an I-JSON forbidden code point
/// - [`JcsError::InvalidNumber`] if a number is not interoperable under JCS
/// - [`JcsError::NestingDepthExceeded`] if the value exceeds [`MAX_NESTING_DEPTH`]
/// Serialize any `Serialize` type to a canonical JSON string.
///
/// # Errors
///
/// Returns:
/// - [`JcsError::Json`] if serialization to JSON fails
/// - [`JcsError::InvalidString`] if a string contains an I-JSON forbidden code point
/// - [`JcsError::InvalidNumber`] if a number is not interoperable under JCS
/// - [`JcsError::NestingDepthExceeded`] if the value exceeds [`MAX_NESTING_DEPTH`]
/// Parse untrusted JSON, apply strict admission checks, and emit canonical
/// RFC 8785 bytes.
///
/// Rejects duplicate property names, validates I-JSON string and number
/// constraints, and enforces [`MAX_NESTING_DEPTH`]. Accepts any valid JSON
/// formatting (including pretty-printed input) and canonicalizes it.
///
/// # Errors
///
/// Returns [`JcsError::Json`] for malformed JSON or duplicate property names,
/// [`JcsError::InvalidString`] or [`JcsError::InvalidNumber`] for I-JSON
/// violations, and [`JcsError::NestingDepthExceeded`] for depth limit breach.
/// Parse untrusted JSON text, apply strict admission checks, and emit a
/// canonical RFC 8785 string.
///
/// # Errors
///
/// Returns the same errors as [`to_canon_bytes_from_slice`].
/// Parse untrusted JSON, apply strict admission checks, and return the
/// canonical RFC 8785 bytes inside a [`CanonicalBytes`] wrapper.
///
/// Prefer this over [`to_canon_bytes_from_slice`] for any path that will
/// feed the bytes into a digest, signature, or receipt primitive — the
/// wrapper makes "came out of JCS" a type-level fact.
///
/// # Errors
///
/// Returns the same errors as [`to_canon_bytes_from_slice`].