XCIM C01 portable consent contract, candidate for draft-saez-xcim-02 Profile: xcim-consent-01. New objects; not reinterpretations of P1/MAIL01. COMMON JSON UTF-8, no duplicate names or invalid Unicode scalar values; only integer number tokens, safe integers 0..9007199254740991, booleans not integers. JCS RFC8785; canonical unpadded base64url RFC4648. SHA256 and Ed25519. All objects closed. Flattened JWS envelope exactly protected,payload,signature. Protected exactly alg=Ed25519,kid,typ=application/xcim-consent+json. No unprotected fields, detached payload, b64=false, key URLs, algorithm fallback. Protected and payload bytes MUST equal their JCS representation. Signature signs ASCII(protected + '.' + payload). kid selected from admitted local issuer policy, never from object-provided keys or discovery URLs. Each signed payload includes profile=xcim-consent-01, kind, issuer. Object commitment H(payload) = b64url(SHA256(UTF8('XCIM-C01-OBJECT') || 00 || JCS(payload))). Signature envelope is not part of H. kind/profile in payload prevent type reuse. Hash/salt 32 octets, signature64. Strings NFC is NOT implicitly applied. Times integer UTC Unix seconds; expiration exclusive. Identifiers exact strings. Issuer/sender/type/policy/log/anchor-profile identifiers absolute HTTPS URIs, no userinfo/query/fragment; do not dereference them to discover authority. Opaque consent_id and event_id use urn:uuid with lowercase UUIDv4 syntax. kid 1..200 ASCII printable characters. Text 1..4096 Unicode scalar values. Integer epochs/revisions start1. Array types 1..64 unique type_id, sorted by ASCII type_id. URI strings ASCII max512. Ordinary envelope max128KiB; anchor_batch envelope max512KiB. JSON depth16; complete evidence max2MiB, batch payload max256KiB, leaves max4096. ADDRESS address_profile=ascii-idna15-1. Trim only outer SP/HTAB, exactly one @. Local part ASCII dot-atom, case/dots/plus preserved, 1..64 octets, no leading, trailing or consecutive dots. Domain UTS46 rev31 Unicode15.1 nontransitional STD3 then IDNA2008 checks; lower-case resulting A-labels, no trailing dot. Labels1..63, domain<=253, fulladdress<=254. No provider alias equivalence. Quoted local parts and SMTPUTF8 unsupported by this address profile, not by an email-message profile. Canonical address is a private verifier input. SENDER binding (kind=sender) Exactly profile,kind,issuer,sender_id,sender_epoch,name,scope, entity_evidence_commitment,issued_at,expires_at. name and scope text identify company/entity and exact brand/service boundaries. Issuer attests authenticated authority of the enrolling actor to represent this entity/scope, retains private evidence. Not sender self-assertion. entity_evidence_commitment commits randomized private evidence under local policy; opaque32bytes, does not claim a universal legal-entity certification. sender_id issuer-assigned stable identifier, never reassign to another entity. Ownership/scope change requires new epoch and new recipient decision; key or domain changes alone do not transfer consent to another organization. An application/ESP/OAuth client/domain is not this identity automatically. Binding finite validity. A grant embeds the exact binding H, name and scope are retrieved from this signed binding and shown in consent UI. ATTESTATION (kind=consent) Exactly profile,kind,issuer,consent_id,sender_id,sender_epoch, sender_binding_commitment,address_profile,recipient_salt,recipient_commitment, types,notice,control,decision,accepted_at,issued_at,expires_at. types array elements exactly type_id,version,title,description. version positive integer; title and description text. Entire definitions signed. notice exactly language,text (language ASCII 1..35, text1..4096). control exactly method,policy_id,observed_at,evidence_commitment. method=oidc-01; policy_id admitted by verifier's issuer policy, not arbitrary. decision=explicit_consent. observed_at<=accepted_at<=issued_at=grant expiry. Grant types finite, no wildcard; promotional and transactional are examples, both require explicit decision. Existing transaction_relationship not accepted. Tuple (issuer,sender_id,sender_epoch,type_id,version) defines immutable title+description. Change requires higher version or newtype_id and fresh decision/newconsent_id. Verify any previously authenticated or policy-known definition; differing meaning at same tuple is conflict, never expansion. Recipient mapping hash uses tag 'XCIM-C01-RECIPIENT' ||00|| JCS(array): [issuer,consent_id,sender_id,sender_epoch,address_profile,recipient_salt, canonical_recipient]. recipient_salt CSPRNG32bytes; generated by issuer from authenticated address observation, never trust a digest supplied by sender. Grant and salt are access-controlled evidence, never put full payload onchain. Notice and exact sender/types are frozen in a server-side consent session. OIDC authorization-code+PKCE, state and nonce bound to session; validate issuer, signature/key/algorithm, aud/azp/sub/nonce/time/address against pinned provider policy. Provider must be authoritative for observed address; email_verified alone insufficient. Admitted policy fixes maximum observation-to-consent age; reject control.observed_at older than that age at accepted_at. Do not infer consent from authentication, registration, purchase or preselected choice. Require separate affirmative user action. Record that same-session authenticated action, address observation, sender binding and type definitions in private evidence. Commit randomized private evidence; no raw token/subject/email in public logs. Commit decision/session/ nonce/receipt atomically. Retry returns same grant; denial terminal for session. One session may grant several types; no requirement to issue per-email objects. New/broader types, sender ownership/scope changes or regrant require new explicit decision and new consent_id. Refresh/login is never renewal. REVOCATION (kind=revocation) Exactly profile,kind,issuer,event_id,consent_id,consent_commitment,sequence, previous_event_commitment,type_ids,actor,method,evidence_commitment, requested_at,effective_at,issued_at. sequence1..64; previous_event_commitment=H(consent) for seq1, else H(prev event). type_ids nonempty sorted unique subset of original grant, none already revoked. actor=recipient|sender|attester. Recipient authenticated withdrawal; sender authenticated surrender/restriction; attester authenticated protective action. method matches [A-Za-z0-9._-]{1,64} and identifies an admitted procedure in local issuer policy; never let sender claim actor=recipient. All events attested/signed by issuer. Evidence commitment binds actor, request nonce, grant, types and request result. consent.issued_at<=requested_at<=effective_at<=event.issued_at. No future-effective events in minimum. effective_at monotonic across sequence; issued_at too. event_id MUST be unique within the lifecycle chain. First committed valid request for overlapping types wins; retries return existing result and cannot append duplicate removal. Disjoint removals serialize. No grant/extension/reactivation transition in event family. At most64 events because every event irreversibly removes at least one of at most64 types. Publish and anchor revocations as immutable history. A verified revocation withdraws positive status immediately even if its anchor is pending. Publishers MUST NOT issue active status for a removed type after committing its withdrawal. CURRENT STATUS (kind=status) Exactly profile,kind,issuer,consent_id,consent_commitment,sender_id,sender_epoch, sequence,event_head,active_type_ids,this_update,next_update. sequence=number of complete revocation events supplied, 0..64. event_head=H(consent) if0 elseH(last). active_type_ids sorted subset of original type_ids equal original minus cumulative revocations. Empty allowed. this_update>=grant issued_at and all revocation issued_at; this_update=object issued_at for each checked leaf. Batch signature role anchor. This bounded full-list inclusion avoids an additional Merkle proof format; more than4096 objects require another batch, not truncation or reset. Only commitments publicly exposed. Persist exact signed objects privately for authorized recipients/verifiers; immutable hash does not ensure availability. ANCHOR EVIDENCE INTERFACE Closed wrapper profile_id,network_id,genesis_hash,anchor_id,block_hash, transaction_hash,record_index,batch_commitment,proof. Wrapper untrusted. proof is profile-specific JSON, bounded complete envelope. network_id,genesis_hash,anchor_id,block_hash,transaction_hash are ASCII1..512; record_index safe nonnegativeinteger, batch_commitment32bytehash, proofJSONobject. All identity and proof fields interpreted only by a pre-admitted profile. Minimum positive query requires actual verified blockchain inclusion/finality for the consent's batch. A fixture, issuer finalized flag, transaction URL, HTTPS success or agreeing RPC answers are insufficient. Verifier policy pins exact network/genesis, anchor authority/location, commitment interpretation and consensus/finality validation method. A valid adapter returns authenticated batch commitment and network/anchor context. No default network, mandatory node operation or automatic discovery is implied. Interoperation requires same admitted anchor profile; unsupported=>indeterminate. Concrete chain proof encodings are separate profiles; this core contract does not claim a production chain adapter or live Amoy evidence. AN01 contract can publish C01 batch digest under a NEW admitted deployment/profile; not AN01-F01 wire compatibility. Previous AS checkpoint bytes MUST NOT be relabeled C01. Grant requires historical anchor; status freshness uses signed snapshots. Revocation anchoring delay policy is explicit in deployment; lack of anchor can never restore a withdrawn type. No universal instantaneous-finality claim. EVIDENCE SET Exactly profile,sender,consent,revocations,status,consent_anchor, revocation_anchors. Firstfour object values JWS except revocations array. consent_anchor exactly batch(JWS),evidence(anchor wrapper). sender,consent,status are JWS; revocations is an array of JWS. Issuer MUST be identical in every sender,consent,event,status,batch payload. All internal sender/epoch/consent identifiers and commitment references MUST match exactly; an internal cross-scope mixture is INVALID_EVIDENCE. revocation_anchors array entries exactly event_id,batch(JWS),evidence(wrapper). Each provided anchor corresponds to one supplied event; uniqueevent_id. Missing revocation anchors marked pending in result, never evidence of active. No email, URL chosen by sender, MSA, MP, DKIM, presentation_id input. Protocol retrieval not bound to SMTP: access-controlled HTTPS/file/outofband. HTTP uses application/json; configured authenticated endpoints only, certificate verification, no redirect/credential leakage, resource bounds, reject SSRF. This core does not standardize discovery or endpoint API/authentication method; both peers preconfigure access. This is explicit deployment configuration, not permission to weaken proof checks or accept unauthorized queries. QUERY AND RESULTS Query authenticated local context: canonical_recipient,issuer,sender_id, sender_epoch,type_id,type_version,evaluation_time,policy and evidence. Type/version must match grant exactly; sender mapping obtained independently by consuming application; body classification external, never certified here. Verify resources/closed types, trusted signatures/roles, sender/receipt scope, recipient commitment, explicitdecision and approved controlprofile, complete terminal lifecycle chain/projection, grant blockchain proof, fresh status, clock/trust bounds and known newer floors. Invalid data cannot alter floors; authenticated restrictive events may be retained before checking their anchors. Clock uncertainty u<=60s. For positive result require this_update<=t-u, t+uINVALID; unsupported profile=>UNSUPPORTED; different queried tuple with otherwise valid evidence=>NO_MATCH; authenticated withdrawal for queried type=>REVOKED, expiry=>EXPIRED; missing/stale/unavailable requiredproof or conflicted trusted history=>INDETERMINATE. Only all mandatory checks positive=>CURRENT. No outcome asserts a message safe, correctly typed, authenticated or deliverable. No universal mail rejection on missing XCIM. Repeated same valid query returns same grant state; doesn't consume consent. TRUST AND BOUNDARIES Policy installed by verifier administrator binds exact issuer, third-party acceptance, key id/publickey/role/issuancewindows/revocation, sender epoch floors, OIDC provider policies, revocation request procedures, supported anchorprofiles, clock and maximum age, endpoint query access and validity. Trusted configuration is not obtained from evidence itself. Role keys may be distinct; no automatic crossrole delegation. Required roles by kind: sender=sender_binding, consent=consent_attestation, revocation=consent_revocation, status=consent_status, anchor_batch=anchor_publication. Signatures must fall within key issuance authorization at the payload issued_at (this_update for status; created_at for batch). Current policy determines whether historical signatures remain acceptable after rotation/compromise. Routine retirement alone need not erase historical evidence. Status key MUST remain authorized at evaluation; its validity end further limits the positive deadline. Key/issuer compromise invalidates current acceptance per authenticated administrator policy. Rotation/recovery newpolicy requires local admin authorization; no TOFU reset. Historic signedbytes stay unchanged. Public blockchain establishes inclusion/order under finality assumptions, not truth of issuer observation, uninterrupted address ownership, or currentstate. No claims of live providers/consensus/independent interoperability from fixtures.