Working draft for public review. Not a stable standard, IETF adoption, certification or production recommendation. Experimental profiles require explicit opt-in.
XCIM Encodings and Recipient Binding
Contents
WD-02 · Normative encoding rules and explicit limits of public projections
1. HTTPS identity issuer normalization
xcim-https-issuer-0.1 is the deliberately narrow URI profile for EVP-01's identity_issuer. It uses URI components from RFC 3986, not browser repair rules. Unsupported forms MUST be rejected, not guessed.
- Require printable ASCII, HTTPS and a DNS A-label hostname. Reject credentials, whitespace/control characters, backslashes, percent escapes, query/fragment delimiters, IP literals, a trailing hostname dot and invalid DNS labels. Internationalized hosts MUST be converted to validated A-labels before this procedure.
- Lowercase the scheme and hostname. Validate a decimal port in 1–65535; remove port 443 and serialize another port without leading zeros. An empty port is invalid.
- Preserve path case and characters exactly. Only ASCII letters/digits, unreserved characters, sub-delimiters,
:,@and/are allowed. Reject path segments exactly.or..; do not collapse repeated slashes or decode/re-encode a path. - Serialize an absent path as
/. A present trailing slash is preserved;/issuerand/issuer/are distinct issuer identifiers.
Thus https://ISSUER.EXAMPLE:443 and https://issuer.example/ normalize to https://issuer.example/. The public EVP field MUST already equal the normalized result. Its commitment is exactly:
base64url(SHA-256(UTF8("XCIM-IDENTITY-ISSUER-0.1\0") || UTF8(normalized_identity_issuer)))
EVP verification_origin follows the same authority validation but contains no path, query or fragment. Serialize it as https://lowercase-host[:nondefault-port], with no trailing slash, following the HTTPS origin model in RFC 6454. Validators MUST compare this exact serialized origin to the session-frozen origin. These EVP rules MUST NOT replace OIDC Core's exact issuer matching or rewrite an already signed object.
2. Commitment scope
| Field | Meaning and verification |
|---|---|
| Receipt, batch, event and state hashes | Publicly reproducible formulas in the core and selected publication profile |
presentation.manifest_hash |
SHA-256 of JCS of the complete inline-signed manifest, including vendor_signature |
EVP identity_issuer_commitment |
Exact normalized-URI recipe above |
| OIDC client and subject commitments; both profiles' nonce commitments | 32-byte issuer-local attestations under an explicitly versioned adapter recipe; not universally reproducible public hashes |
EVP evidence_fingerprint |
Adapter/revision-specific validated representation; cross-adapter recipe remains an experimental review gate |
recipient.pairwise_id |
Application-scoped private pseudonym, not a public email-to-ID lookup |
For an issuer-local commitment, the producer MUST document the source representation, byte encoding, domain separation, scoping and salt/key lifecycle in its versioned adapter contract. Receivers MUST enforce byte length, signed profile/context and required client-commitment equality; they MUST NOT invent a common preimage recipe or compare opaque commitments across issuers/adapters. A new documented recipe does not retroactively reinterpret historical values. Fixtures using repeated marker bytes are synthetic projection tests, not derivation vectors.
New issuance claiming WD-02's commitment baseline MUST select xcim-identity-commitments-wd02 in the versioned adapter contract identified by its policy and adapter_version. This is a producer recipe, not a new receipt member. Its closed private input objects are:
| Commitment | Exact private JCS input members | UTF-8 domain including terminal NUL |
|---|---|---|
| OIDC application/evidence client commitment | issuer, client_id, app_id, app_epoch |
XCIM-OIDC-CLIENT-WD02\0 |
| OIDC subject commitment | issuer, subject, app_id, app_epoch, salt |
XCIM-OIDC-SUBJECT-WD02\0 |
| OIDC or EVP nonce commitment | app_id, app_epoch, nonce |
XCIM-NONCE-WD02\0 |
Each result is base64url(SHA-256(domain || J(private_input))). Strings are exact validated values; the OIDC issuer is matched exactly under OIDC Core, not normalized by the EVP URI algorithm. app_epoch is a positive safe integer. salt and nonce encode exactly 32 independently random bytes as canonical unpadded base64url; the subject salt is retained privately for audit and the nonce is the session-frozen nonce used for identity validation. No private input object is added to the public receipt or log. The same client recipe/input MUST produce both client commitment fields. Separate application epochs or scopes change these commitments.
Historical receipts and other explicitly accepted adapter recipes remain issuer-local attestations, not claims to this new derivation recipe. A receiver MUST use the recorded adapter contract rather than recomputing historical fields with WD-02's domains. The bundled wd02-commitment-recipes.json contains synthetic private inputs and exact digests reproduced in Python and Node. It does not supply secrets or validate an actual identity assertion.
The baseline pairwise derivation is HMAC-SHA-256(issuer_app_pairwise_key, UTF8(canonical_email)), encoded as unpadded base64url. The key MUST be secret and independently scoped to each issuer/application or explicitly declared privacy domain. The canonical email is the exact authorized delivery address, not a provider's comparison alias. Key changes require authenticated mapping/lifecycle handling; a receiver MUST NOT infer that a changed ID represents the same recipient. The bundled helper and fixed test-only keys illustrate these bytes, not production key distribution.
3. Email canonicalization
The xcim-email-0.1 profile accepts one addr-spec without display names, comments or angle brackets. Trim only leading/trailing SPACE/HTAB; reject CR/LF, other controls and internal whitespace. Accept an ASCII dot-atom local part of at most 64 characters, without leading/trailing or consecutive dots; preserve its case, dots and plus tags. Quoted local parts and SMTPUTF8 are not supported by this profile.
Process the domain using UTS #46 non-transitional mapping with STD3 rules, serialize lowercase ASCII A-labels, and reject literals, underscores, trailing dots, invalid labels, domains longer than 253 ASCII characters and addresses longer than 254 characters. The reference runner pins Python idna==3.10, which fixes its IDNA/Unicode tables; another implementation MUST reproduce the published positive and negative vectors. Provider-specific equivalence rules remain independently versioned and MUST NOT change this canonical delivery address.
4. Recipient verification boundary
A receiver knows the intended SMTP envelope recipient or the authenticated list-evaluation target. A signed pairwise ID is not enough to establish that match because the receiver does not hold the issuer's pairwise secret. A receiver MUST obtain an authenticated mapping bound to the issuer, application/epoch, exact canonical target and pairwise ID through its explicitly declared integration contract. A sender assertion, arbitrary lookup response or copied header is insufficient. Missing, untrusted or unavailable mapping MUST withhold PASS.
This draft does not standardize a portable recipient-mapping attestation or distribute pairwise keys to receivers. Header integrations therefore remain shadow-mode until their authenticated mapping and message-binding contracts are reviewed and exercised. No implementation can claim end-to-end receiver conformance solely from the public fixture verifiers.
The historical message-presentation digest vector is exactly:
SHA-256(UTF8("XCIM-MESSAGE-RECIPIENT-0.1\0") || UTF8(canonical_envelope_recipient) || salt_16_bytes)
Its unpadded base64url digest and independently random 16-byte salt belong only in the private message presentation, not public logs. The digest detects changes to that presentation's recipient bytes; it is not an issuer signature or an authenticated pairwise mapping. A malicious sender can recompute a digest and therefore cannot be constrained by this digest alone. check_message_recipient_vector.py tests bytes and mutations, not recipient authorization.
5. Standalone checks
From the extracted bundle root, install tools/requirements-ci.txt, make Node.js available, then run:
python tools/xcim_check.py
python tools/check_email_normalization_vector.py
python tools/check_message_recipient_vector.py
python tools/shadow_mode_adapter.py examples/xcim-shadow-mode.eml
All fixtures are synthetic, use explicitly public test-only key material and run without a private product workspace or live issuer. A successful fixture run is not current permission, chain verification, accreditation or independently operated interoperability.