Skip to content

RFC 8785 JSON Canonicalization (JCS)

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.

  1. UTF-16 Code Unit Property Sorting (§3.2.3): Object keys are sorted lexicographically by their UTF-16 code units:
    keys.sort_by(|a, b| a.encode_utf16().cmp(b.encode_utf16()));
    This ensures identical ordering between ECMAScript and Rust engines, even for astral plane characters.
  2. Whitespace Suppression (§3.2.1): No whitespace characters (0x20, 0x09, 0x0A, 0x0D) are emitted outside string literals. Structural tokens ({, }, [, ], :, ,) are immediately adjacent.
  3. IEEE-754 binary64 Floating Point Formatting (§3.2.2.3): All numbers are serialized using the ryu-js crate, matching ECMAScript Number.prototype.toString():
    • -0.0 is 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. 9007199254740993 serializes as "9007199254740992").

To prevent parser differential attacks (“JSON Smuggling”), resolver-core integrates strict_json.rs.

  • Implementation: Implements custom serde Visitor and MapAccess deserializers.
  • 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:
    "duplicate JSON object member"
    This eliminates “last-key-wins” vs “first-key-wins” parser exploits between gateways, proxies, and verifiers.

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)
  • 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).
  • 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-2019 is 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:

  1. 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.
  2. Selective Disclosure:
    • ecdsa-sd-2023, bbs-2023
    • Status: Returns DiError::Unsupported.
  3. RSA Cryptography:
    • RSA multicodecs (0x1205) and RS256/PS256 JOSE algorithms.
    • Status: Returns CryptoError::Unsupported.
  4. Non-P256 NIST Curves:
    • P-384 (0x1201), P-521 (0x1202).
    • Status: Returns CryptoError::Unsupported.
  5. JWS Critical Extensions:
    • Compact JWS headers with a non-empty crit array are rejected with:
      CryptoError::Unsupported("JWS critical extensions are not supported by this verifier")
  6. Unsecured JWS:
    • alg: "none" is rejected at the parser level with CryptoError::Malformed.