RFC 8785 JSON Canonicalization (JCS)
3. Cryptographic Suites & Data Integrity
Section titled “3. Cryptographic Suites & Data Integrity”3.1 RFC 8785 JSON Canonicalization Scheme (JCS)
Section titled “3.1 RFC 8785 JSON Canonicalization Scheme (JCS)”DID.is implements RFC 8785 directly via jcs.rs under the versioned profile rfc8785-binary64-v1.
3.1.1 Canonical Serialization Invariants
Section titled “3.1.1 Canonical Serialization Invariants”- UTF-16 Code Unit Property Sorting (§3.2.3):
Object keys are sorted lexicographically by their UTF-16 code units:
This ensures identical ordering between ECMAScript and Rust engines, even for astral plane characters.keys.sort_by(|a, b| a.encode_utf16().cmp(b.encode_utf16()));
- Whitespace Suppression (§3.2.1):
No whitespace characters (
0x20,0x09,0x0A,0x0D) are emitted outside string literals. Structural tokens ({,},[,],:,,) are immediately adjacent. - IEEE-754 binary64 Floating Point Formatting (§3.2.2.3):
All numbers are serialized using the
ryu-jscrate, matching ECMAScriptNumber.prototype.toString():-0.0is serialized as"0".- Smallest subnormal positive:
0x0000000000000001$\to$"5e-324". - Scientific notation thresholds: numbers $10^{21} \le |x|$ or $|x| < 10^{-6}$ emit exponential notation.
- Large integer rounding: integers exceeding $2^{53}-1$ ($9,007,199,254,740,991$) round to nearest binary64 representable float (e.g.
9007199254740993serializes as"9007199254740992").
3.2 Serde-Level Duplicate Key Rejection
Section titled “3.2 Serde-Level Duplicate Key Rejection”To prevent parser differential attacks (“JSON Smuggling”), resolver-core integrates strict_json.rs.
- Implementation: Implements custom serde
VisitorandMapAccessdeserializers. - Escape Normalization: Duplicate checking occurs after JSON unicode escape sequence normalization. For example,
{"alg": "none", "\u0061lg": "EdDSA"}is detected as a duplicate key collision and rejected before parsing can conclude. - Fail-Closed Result: Any duplicate member anywhere in the object tree terminates parsing with:
This eliminates “last-key-wins” vs “first-key-wins” parser exploits between gateways, proxies, and verifiers."duplicate JSON object member"
3.3 Supported Data Integrity Suites
Section titled “3.3 Supported Data Integrity Suites”Data Integrity Verification Flow: 1. securedDocument ├── Strip 'proof' property ──► unsecuredDocument └── Extract 'proof' object ──► proofConfig (proof minus 'proofValue') 2. Compute Transformed Hash: hashDocument = SHA-256(JCS(unsecuredDocument)) 3. Compute Proof Config Hash: hashProof = SHA-256(JCS(proofConfig)) 4. Combine Digests: signedData = hashProof || hashDocument (64 bytes) 5. Cryptographic Verify: Verify(key, signedData, proofValue)3.3.1 eddsa-jcs-2022
Section titled “3.3.1 eddsa-jcs-2022”- Normative Base: W3C Data Integrity EdDSA Cryptosuites v1.0.
- Key Cryptography: Ed25519 (32-byte public key).
- Proof Structure:
type:"DataIntegrityProof"cryptosuite:"eddsa-jcs-2022"proofPurpose:"assertionMethod"proofValue: Multibase base58btc ('z'prefix) encoded 64-byte Ed25519 signature.
- Hashing Specification: $$\text{HashData} = \text{SHA-256}(\text{JCS}(\text{proofOptions})) \parallel \text{SHA-256}(\text{JCS}(\text{unsecuredDocument}))$$
- Verification: Evaluated via
ed25519_dalek::VerifyingKey::verify_strict(&HashData, &signature).
3.3.2 ecdsa-jcs-2019
Section titled “3.3.2 ecdsa-jcs-2019”- Normative Base: W3C Data Integrity ECDSA Cryptosuites v1.0.
- Key Cryptography: NIST P-256 (secp256r1) ONLY.
- Proof Structure:
type:"DataIntegrityProof"cryptosuite:"ecdsa-jcs-2019"proofPurpose:"assertionMethod"proofValue: Multibase base58btc ('z'prefix) encoded 64-byte IEEE P1363 $r \parallel s$ signature.
- Signature Normalization: Signatures are normalized to the lower-$s$ form ($s \le \frac{n-1}{2}$) via
p256::ecdsa::Signature::normalize_s()prior to verification to defeat ECDSA signature malleability. - Algorithm Constraint: If
ecdsa-jcs-2019is used with a secp256k1 key, verification explicitly fails with:DiError::Unsupported("ecdsa-jcs-2019 with a secp256k1 key is not supported (P-256 only)")
3.4 JOSE Profiles (VC-JOSE vs. Legacy VC-JWT 1.1)
Section titled “3.4 JOSE Profiles (VC-JOSE vs. Legacy VC-JWT 1.1)”DID.is strictly isolates VC-JOSE (W3C VC DM 2.0) and legacy VC-JWT (W3C VC DM 1.1) to eliminate cross-profile claim injection.
| Attribute | VC-JOSE Profile (vc+jwt) |
Legacy VC-JWT 1.1 Profile (JWT) |
|---|---|---|
| Normative Reference | W3C VC DM 2.0 / IETF RFC 7519 | W3C VC DM 1.1 / W3C Implementation Guidance |
Header typ |
MUST equal "vc+jwt" |
MUST equal "JWT" |
| Context | @context contains credentials/v2 |
vc["@context"] contains credentials/v1 |
| Payload Structure | Direct JSON object representing the credential | Top-level claims wrapper containing nested vc object |
| Claim Mirroring | N/A (Claims reside directly at top level) | MUST mirror sub == vc.credentialSubject.id, iss == vc.issuer, jti == vc.id, nbf == vc.issuanceDate, exp == vc.expirationDate |
Top-Level vc Claim |
Prohibited (fails with MALFORMED) |
Mandatory (fails with MALFORMED if absent) |
Top-Level vp Claim |
Prohibited (fails with MALFORMED) |
Prohibited |
| Multi-Subject Policy | Single credentialSubject object/id | Arrays with $>1$ subject fail closed as MALFORMED |
3.5 Explicit Boundaries & Unsupported Suites
Section titled “3.5 Explicit Boundaries & Unsupported Suites”To ensure absolute cryptographic predictability, resolver-core returns explicit failure statuses rather than guessing:
- RDFC-1.0 Suites:
eddsa-rdfc-2022,ecdsa-rdfc-2019,Ed25519Signature2020,JsonWebSignature2020- Reason: Require JSON-LD expansion and RDF Dataset Canonicalization (W3C RDFC-1.0), which DID.is intentionally does not bundle.
- Status: Returns
DiError::Unsupported.
- Selective Disclosure:
ecdsa-sd-2023,bbs-2023- Status: Returns
DiError::Unsupported.
- RSA Cryptography:
- RSA multicodecs (
0x1205) and RS256/PS256 JOSE algorithms. - Status: Returns
CryptoError::Unsupported.
- RSA multicodecs (
- Non-P256 NIST Curves:
- P-384 (
0x1201), P-521 (0x1202). - Status: Returns
CryptoError::Unsupported.
- P-384 (
- JWS Critical Extensions:
- Compact JWS headers with a non-empty
critarray are rejected with:CryptoError::Unsupported("JWS critical extensions are not supported by this verifier")
- Compact JWS headers with a non-empty
- Unsecured JWS:
alg: "none"is rejected at the parser level withCryptoError::Malformed.