Statement Credential (VSC)

Status as of Eucalyptus (2026-10-07): in the spec and in every implementation. The VSC landed on the spec’s main on 2026-09-15 and is part of the current DTG Credentials Core Specification, Working Draft 0.6.0 (milestone tags v0.4.0 2026-09-15 and v0.5.0 2026-09-22; main reached 0.6.0 on 2026-09-28 and is not yet tagged — there is no WD03 tag, although the spec’s contributor notes call the draft that removed the old type strings “WD03”). It is implemented in dtg-credentials 0.12.0 (2026-09-30), in the VTI community service (#1859, 2026-09-30) and in OpenVTC (#397, 2026-10-01), all shipped in the coordinated VTI-Eucalyptus release; the Berkman Klein Keyring phone wallet has a merged migration plan. The September 2026 wiki read this page as “where the spec is going”; it is now where implementations are.

The Verifiable Statement Credential is one W3C type carrying a signed statement by one DTG node about another: a subject, a predicate from a governed vocabulary, and an object. “I inspected this party’s passport”, “this party attended this event”, “I witnessed this party issue this credential”, “I endorse this party’s skill” — each is a statement a verifier reads and either believes or does not. Rather than mint a type per predicate, the spec defines one type and lets a predicate profile fix each predicate’s constraints.

Why one type instead of many

Every DTG credential is either a statement or an establishment, and the difference is in what a verifier has to do. If verifying means checking the signature, checking status and reading the claim, it is a statement. If the verifier must do more — complete an edge from two halves, match a consent digest, resolve a chain, contain a scope, hand it to a policy enforcement point — the credential establishes something and keeps a concrete subtype: the VRC, VMC, VDC, VAC and VIC. The VPC is statement-shaped but stays concrete because its issuer is the persona identifier, which carries correlation-scope semantics.

So the WD02 EndorsementCredential and WitnessCredential subtypes became the first two profiles, dtg:endorses (the VEC) and dtg:witnessed (the VWC). The names VEC and VWC survive for the profiles; the type strings are removed, so a VSC carries exactly one channel of meaning — its predicate — and a type string and a predicate can never disagree. The type count went from eight to seven.

A statement is evidence. Governance turns evidence into establishment: a VTC may weigh an observation statement when admitting a member, an isHuman statement when deciding whether a membership is a PHC, a role statement when deciding to issue a VAC. The statement never establishes the membership, personhood or authority itself.

Shape (WD 0.6.0)

  • @context — https://www.w3.org/ns/credentials/v2 first, https://registry.trustoverip.org/dtg/context/v1 second, compared byte-exact
  • type includes StatementCredential and no other concrete subtype; the predicate, not the type array, identifies the profile
  • issuer — the party making the statement; issuerScope — REQUIRED, the issuer’s declared correlation-scope; a profile MAY set a minimum and a verifier MUST NOT accept a narrower declaration
  • taskContext — OPTIONAL unless the profile requires it, and then taskDigestMultibase is REQUIRED with it (trust-task-context-binding)
  • credentialSubject.id — the node the statement is about
  • credentialSubject.predicate — an absolute IRI; no CURIEs or JSON-LD terms on the wire
  • credentialSubject.object — exactly one of id (a DID or IRI), digestMultibase (another credential, per the spec’s Digest Encoding), or value (a payload whose schema the profile states)
  • further members as the profile defines; verifiers MUST ignore members a profile does not define, so a profile can add optional members without breaking older verifiers
  • proof — a W3C Data Integrity proof (DataIntegrityProof, eddsa-jcs-2022 RECOMMENDED), as for every DTG credential since #56

A VSC is unilateral — its issuer alone signs it. A predicate that is only true when the counterparty agrees (a relationship, a membership, an appointment) is an edge and belongs with an acknowledgement half, not in a profile. A VSC’s issuer is part of the statement: an implementation assembling statements into a graph MUST keep each credential as the unit of attribution, and flattening them into bare subject–predicate–object triples is not a conforming representation.

Fail closed on predicates

A verifier MUST reject a VSC whose predicate is not an absolute IRI (a compact form is malformed, not unknown), and MUST reject one whose IRI is not in a vocabulary the verifier has been configured to accept — configuration informed by the governance frameworks and trust registries it relies on, never derived from the credential. It MUST NOT process an unrecognized predicate as a generic statement, infer meaning from its spelling, or accept it on the strength of an owl:sameAs published by anyone. Comparison is byte-exact on NFC-normalized IRIs; predicates resolve to their definitions at configuration time, never at verification, so verifying needs neither the credential’s @context nor a JSON-LD processor.

The three DTG namespaces version differently (#64): a predicate carries its own integer path segment — .../vsc/witnessed/1, .../vsc/witnessed/2 — each <name>/<n> immutable and distinct; vocabulary (types and properties such as DTGCredential, taskContext) lives at the unversioned https://registry.trustoverip.org/dtg/credentials#, where a changed meaning would be a new term; the context is one frozen document per vN, every prior version served forever, the v1 digest pinned in the spec (zQmSWyCagdx8oPfXn3piSUx6yqVy5MZ7ZG7nW1TC64QvKZh).

The rationale is explicit: a well-formed statement under an unrecognized predicate is the intended shape of an attack that names authority, membership, or personhood in a string. Hence the type-level bound: a VSC attests; it never establishes. Whatever its predicate says, a verifier MUST NOT treat a VSC as conferring representation, authority, membership, admission or a governed status, nor as proof that a trust task completed. “A predicate named mayActFor is a string.” Every profile must state what a pass means and what it explicitly does not.

The core profiles and the registry

The spec names two core profiles and, since #71 (2026-09-30), does not restate their members: the registry entry at each IRI is the normative definition, and a verifier configures against the entry.

  • https://registry.trustoverip.org/dtg/vsc/endorses/1 (VEC) — evidence; object.value structured by the community’s endorsement vocabulary; taskContext OPTIONAL; no minimum issuer scope. A pass establishes that the issuer endorsed the subject with that payload — not that it is accurate, that the issuer is qualified, or that the subject holds any status.
  • https://registry.trustoverip.org/dtg/vsc/witnessed/1 (VWC) — evidence weighed by the community whose witnessing policy applies; object.digestMultibase names the witnessed edge credential; taskContext and taskDigestMultibase REQUIRED; issuer directed at minimum; optional witnessContext (event, sessionId, method, all terms in the v1 context); credentialSubject.id MUST be the issuer of the credential the object names; one VWC per direction of a witnessed edge.

The profiles live in the DTG VSC Predicate Registry (trustoverip/dtgwg-vsc-registry, served at https://registry.trustoverip.org/dtg/vsc/): a repo-driven registry on the model of the ToIP glossary, one definition file per predicate in the nine-point profile format, generated into a human-readable vocabulary and a machine-readable accept-list.json for verifiers, governed by pull request under stated admission criteria. It also hosts and freezes the credential @context; the spec’s editors decide what a context version contains. The registry does not tag releases, so a verifier pins the accept list’s commit. Beyond the two the spec names, the registry defines two more predicates that every Eucalyptus implementation treats as core:

PredicateobjecttaskContextmin. issuerScopeWhat a pass means
endorses/1valueoptional—the issuer endorsed the subject with that payload
witnessed/1digestMultibaseREQUIREDdirectedthe issuer observed the subject issue the referenced credential in the cited exchange
vetted/1valueREQUIREDdirectedthe issuer checked the subject’s claimed identity in a vetting session, for the community object.value.community names
presented/1digestMultibaseREQUIREDdirectedthe issuer observed the subject present the referenced credential in the cited exchange (the subject is the referenced credential’s subject, not its issuer)

vetted/1 admits two issuers: an eligible member vetter, whose statement counts toward admission, and the community itself, recording its own identity check under its own DID. The spec carries the identity-vetting profile as a long worked example (#50, aligned with the registry’s vetted/1 in #70) because it exercises every member a profile must state and shows why a use case gets its own predicate rather than a payload smuggled into endorses: a vetting statement records a procedure — who checked, by what method, against which document classes, on what date — and “the vetter endorses the applicant” is not what the vetter did. Its object.value carries community, method, documentClasses, claimsVerified, livenessConfirmed, a salted identityCommitment (so several vetters’ statements can be shown to concern one claimed identity without the community learning it), cardDigestMultibase, declaredRelationship and an optional attestationTextDigest; a pass establishes that the named member states it carried out the described check in the cited exchange, and not that the identity is true, that the vetter was eligible, that the check was competent, or that anyone was admitted. See peer-identity-vetting.

Community predicates

A VTC or VTN defines its own predicate by publishing, under a namespace it controls, the nine things a profile must state — IRI and labeled meaning; evidence vs assertion of status; permitted object kinds; subject–object relationships; extra members; whether taskContext (and so taskDigestMultibase) is required; minimum issuer scope; who may issue; what a pass establishes and does not. A verifier not configured for that namespace rejects the credential however good its signature — the intended behavior, since which vocabularies count is decided by the governance a verifier relies on, not by an issuer. Admission to the registry is a convenience, not a gate: a community can issue under its own IRI, and verifiers can accept it, without ever seeking admission; the registry curates a shared default set once a predicate is in use by more than one community.

In implementations (Eucalyptus)

dtg-credentials 0.12.0 (2026-09-30, “conform to the v1 context”) is a wire break with no upgrade ordering: every credential 0.11 emitted is refused, and the spec makes pre-Implementers-Draft credentials non-conformant, so deployments re-issue. It adds DTGCredentialType::Statement, StatementObject { Id | DigestMultibase | Value } (exactly one, else refused), check_predicate_iri (absolute, NFC, byte-compared; dtg:witnessed as a CURIE is InvalidPredicate), constants ENDORSES_V1, WITNESSED_V1, VETTED_V1, PRESENTED_V1 with PredicateProfile::core holding each one’s object kinds, taskContext requirement and minimum scope, and constructors new_vsc, new_endorses_vsc, new_witnessed_vsc, new_vetted_vsc, new_presented_vsc — the witnessed and presented ones take the referenced credential in wire form and read the subject from it, so the subject–object rule holds by construction. PredicateAcceptList fails closed, built from_iris or from_registry_json over the registry’s accept-list.json with the statuses a verifier admits; accept(&vsc) matches exactly, follows no equivalence, and applies the entry’s constraints. EndorsementCredential, WitnessCredential and RCardCredential are gone and refused at parse. 0.13.0 (2026-10-01) is a dependency bump with no wire change.

The VTI community service (#1859) issues and verifies every statement as a StatementCredential. The community’s registered “endorsement types” (vtc/endorsement-types/*, names kept from before) are the predicates it accepts — a typeUri must be a registry IRI or one in a namespace the community controls — and a new community seeds the four core predicates once at first boot, as a fail-closed accept list: a Vetting Statement at join, a witness at personhood assert, or any statement in a presented vp_token under an unregistered predicate is refused, never processed generically. vtc/endorsements/issue/0.1 mints a VSC under a registered predicate with the community as issuer and issuerScope: public; under vetted/1 the community records its own identity check, citing the issue request as taskContext/taskDigestMultibase; witnessed/1 and presented/1 are predicateNotIssuable by the community, because those are made by the party that ran the exchange. Operators with custom Rego policies written against the old shapes find them silently denying — Rego treats a missing field as undefined — and the VTI ships a migration page for them.

OpenVTC (#397) claims statements by predicate, never by type, with a fail-closed core accept list; the vetter’s Vetting Statement is a vetted/1 VSC issued directed, with the body at object.value, citing the signed vetting/session document by taskContext and taskDigestMultibase — the desk keeps the signed session and refuses to attest a session without one, the applicant refuses a statement that does not cite the session it received. Stored statements and vetter grants in a pre-v1 shape are set aside on load with a notice. #408 (2026-10-02) keeps the community’s own vetted/1 about the member as a new CredentialKind::CommunityVetting, lists it as “Statement: vetted (verified by the community)” and offers it as personhood evidence like any stored membership credential; it does not activate a membership.

Keyring gives the implementer’s-eye view. Its docs/plans/vsc-migration-plan.md (merged 2026-09-18, read against WD 0.4.0) moves every witness credential the wallet issues, carries, verifies and displays from the WD02 type to dtg:witnessed, and makes three observations worth keeping: the name VWC is not deprecated, only the type string, so the ~2,200 occurrences of “VWC” across the codebase are not a migration surface (the wire shape lives in sixteen files); “is this a witness credential?” stops being a question about type and becomes one about credentialSubject.predicate; and, because Keyring signs with JSON-LD canonicalization, a VSC member with no JSON-LD term “does not exist in the signed graph” — so vocabulary terms must land before members do, guarded by CI. The plan was a proposal with no code at merge; by 2026-09-30 Keyring’s vetting statement was being judged by the VTI 0.47 SDK as a vetted/1 statement in its card-verify harness.

See also: endorsement-credential, witness-credential, credential-categories, dtg-credentials-overview, dtg-credential-spec, trust-task-context-binding, peer-identity-vetting