diff --git a/content/concepts/authority-credential.md b/content/concepts/authority-credential.md new file mode 100644 index 0000000..eb68809 --- /dev/null +++ b/content/concepts/authority-credential.md @@ -0,0 +1,59 @@ +--- +title: "Authority Credential (VAC)" +type: concept +tags: [credentials, dtg, authority, attenuation, agents, data-rooms, spec] +date-updated: 2026-09-18 +sources: [dtg-credential-spec, dtg-credentials, verifiable-trust-infrastructure] +--- + +# Authority Credential (VAC) + +The Verifiable Authority Credential is the first DTG credential that **confers** something rather than **attests** something. Added in Working Draft 02 of the [[dtg-credential-spec|spec]] (PR #29, 2026-09-07) and hardened on `main` days later (#39, #40, #41), it answers one question: *may this party do this thing, here, as itself?* + +## Why it exists + +The WD01 catalogue had six credentials and none conferred permission, so implementations packed "may write" into a [[endorsement-credential|VEC]]. The spec calls that a category error: an endorsement is a statement *about* a party, which a verifier weighs for itself; an authority credential is a statement *to* a verifier — the party governing the scope has already decided. Conflating them "leaves verifiers to infer permission from adjectives." + +A VAC therefore stands **outside** the functional [[credential-categories|categories]]: it neither forms a graph edge nor annotates one. It states what a party may do within a scope some node governs. + +## What it contains + +- `type` includes `AuthorityCredential`; `issuer` is the party governing the scope (a VTC, VTN, a shared resource or service, or a holder attenuating what it holds); `credentialSubject.id` is the party receiving authority +- `authority.scope` — the DID or URI the authority applies to; exact match unless the governing party publishes a containment rule +- `authority.actions` — permitted actions from a vocabulary the governing party defines. MUST NOT be empty (empty confers *nothing*), and no action implies another — `"admin"` does not grant `"write"` unless both are listed. The vocabulary is deliberately local: `"write"` from one governing party has no defined relation to `"write"` from another +- `authority.parent` — for an attenuated VAC, the **digest** of the VAC it was narrowed from +- `authority.maxAttenuation` *(main, #40)* — how far a chain may extend below this VAC; `0` forbids attenuation +- `validUntil` REQUIRED; `credentialStatus` CONDITIONAL *(main, #39)* + +## Attenuation: equipping an agent with less than you hold + +A holder MAY issue a further VAC conferring a **subset** of what they hold, without the governing party — which is what lets a person equip an AI agent with four hours of read-only access to one room instead of lending it their own standing authority. An attenuated VAC MUST set `issuer` to the parent's `credentialSubject.id` (only the party a VAC was issued to may attenuate it), carry `authority.parent` as the parent's digest, and never add an action, widen scope, or outlive its parent. + +Three rules make this safe: + +- **Verifiers walk the whole chain** back to a VAC issued by the governing party and reject any link that widens or whose issuer is not its parent's subject. A verifier that checks only the presented credential "has verified nothing" — anyone can mint a perfectly valid VAC naming any scope. +- **The holder presents the chain; the verifier never fetches it.** `parent` is a digest, so there is nothing to fetch: verification cannot depend on network availability, cannot be induced to hit an address of the holder's choosing, and signals nothing to whoever hosts an identifier. Because the digest excludes `proof`, re-signing a parent leaves its children intact while re-issuing it with different claims orphans them. +- **Depth is bounded at 8** as a denial-of-service bound. On `main`, `maxAttenuation` adds a *policy* limit for sensitive actions. Attenuation is otherwise permitted by default — the opposite of the [[delegation-credential|VDC]]'s opt-in re-delegation — because forbidding it does not stop a holder equipping an agent; it makes them lend their key instead. + +## Not a bearer credential (`main`, #41) + +WD02 shipped with an OPTIONAL `audience`, and a VAC without one was by omission a bearer token. `main` fixes this as the VDC already had: a verifier MUST NOT accept a party as holding a VAC's authority unless it **demonstrates control of the presented VAC's `credentialSubject.id`** at the time of the request. Only the leaf's subject demonstrates anything; the parties above are not in the loop, which is the point of attenuation. With that rule `audience` could only repeat the subject or be unsatisfiable, so it was **removed** — where a presentation may be *sent* is a trust-task question. The stakes exceed the VDC's: a captured presentation is a captured *chain*. + +## Withdrawal (`main`, #39) + +A VAC is withdrawn by expiry or by its issuer's revocation — nothing else, because nothing about the subject's current standing is consulted at verification. Expiry is primary (`validUntil` REQUIRED, kept short); `credentialStatus` covers what expiry cannot, is CONDITIONAL on the governing party's freshness window, and MUST be checked on every chain link that carries it. **Revocation cascades**: revoking a VAC withdraws everything attenuated from it, so a governing party withdraws derivations it never saw. The trade is stated plainly: a chain with no status cannot show that an ancestor was revoked, so exposure is bounded by the shortest `validUntil` in the chain. + +## What a VAC is not + +- **Not delegation.** A VAC authorizes acting *in one's own name*, never on behalf of another — that is the [[delegation-credential|VDC]]. The test for an agent is *whose name is the act in?* An agent that acts as itself gets an attenuated VAC; one whose acts should be attributed to its principal gets a VDC. A VDC never supplies authority; a VAC is what satisfies the permission check a VDC defers. +- **Not membership.** A VAC MUST NOT be read as evidence of membership, nor a [[membership-credential|VMC]] as evidence of authority. Where a verifier needs both in a [[zero-knowledge-proofs|ZK presentation]] with the subject withheld, the presentation MUST prove the two credentials share a subject — else two parties pool one's membership with the other's authority. On `main` a governing party MAY also require an attenuated VAC's subject to independently qualify. + +## In the data room + +The VAC is the permission model of [[data-rooms]]: a room is a DTG node with its own DID that admits a member by [[invitation-credential|VIC]], forms the membership edge by VMC pair, and issues **VACs for `read` / `write` / `curate` / `admin`** one level down. A member's VTA then attenuates an hours-scoped, read-only VAC for the member's agent, and the room's verifier walks the chain back to itself. + +## Implementation status + +[[dtg-credentials]] tracks the VAC closely: 0.6.0 (2026-09-03) added `new_vac`, `attenuate` and `authority::verify_chain` (seven rules, depth 8, bearer-side resolution, empty `actions` refused) against the then-open spec PR; 0.7.0 made `parent` a digest and `validUntil` required (WD02); 0.8.0 requires the leaf to grant to the *presenter* and removed `audience` (spec #41) — both known consumers, including `vti-rooms-dtg`, had already patched that gap by hand. Not yet implemented: `maxAttenuation` (#40) and status resolution / revocation cascade (#39); `credentialStatus` is settable since 0.9.0 but never resolved. + +See also: [[delegation-credential]], [[credential-categories]], [[dtg-credentials-overview]], [[data-rooms]], [[endorsement-credential]] diff --git a/content/concepts/bip32-key-derivation.md b/content/concepts/bip32-key-derivation.md index a1cfbf6..491590e 100644 --- a/content/concepts/bip32-key-derivation.md +++ b/content/concepts/bip32-key-derivation.md @@ -1,9 +1,9 @@ --- title: "BIP-32 Key Derivation" type: concept -tags: [cryptography, keys, bip32, bip39] -date-updated: 2026-08-19 -sources: [verifiable-trust-infrastructure, openvtc] +tags: [cryptography, keys, bip32, bip39, post-quantum] +date-updated: 2026-09-18 +sources: [verifiable-trust-infrastructure, verifiable-trust-agent, openvtc] --- # BIP-32 Key Derivation @@ -36,11 +36,14 @@ m/26' ← Root for First Person Network ## Key Types -The ecosystem derives three types of keys from this tree: +The ecosystem derives four kinds of key from this tree: - **Ed25519** — for signing credentials, DID operations, and authentication -- **X25519** — for key agreement (DIDComm encryption) +- **X25519** — for key agreement (DIDComm and TSP encryption) - **P-256 (ECDSA)** — for compatibility with systems requiring NIST curves +- **ML-DSA-44 / ML-DSA-65** — [[post-quantum-cryptography|post-quantum]] signing keys, since September 2026 (VTI #1505). FIPS 204 key generation takes a 32-byte seed, so the chain can produce one — but the obvious implementation (hand the SLIP-0010 output straight to the key constructor, as Ed25519 does) would make the ML-DSA seed *equal* the Ed25519 private key at the same path, so compromising either yields the other. The derivation therefore follows P-256's construction: HMAC-SHA512 over the derived key and chain code under a **per-parameter-set label**, so ML-DSA-44 and ML-DSA-65 at the same path are independent of each other and of the classical key. The `-priv-seed` multicodecs are what let such a key be written down and re-derived. Every key record now carries the algorithm it was minted with (#1532). + +DID templates declare which algorithm each key slot uses, and since the **did-templates 3.0** task family a template may name a **third slot** — a post-quantum signing key beside the classical signing / key-agreement pair — which is the shape a hybrid-credential issuer needs (#1530, #1538, #1554). ## How OpenVTC Uses Derivation Paths @@ -72,8 +75,10 @@ The seed is the crown jewel. In the OpenVTC ecosystem, it can be stored in: The [[verifiable-trust-agent|VTA]] adds another layer: it acts as a signing oracle, so applications never see the keys at all — they submit payloads and get signatures back. -## The Exceptions (August 2026) +## The Exceptions (August–September 2026) + +Derivation from one seed is the rule, but the [[verifiable-trust-agent|VTA]] now deliberately holds two kinds of key *outside* the tree: **non-extractable internal signing keys** — generated from a CSPRNG with no derivation path, stored in their own keyspace that is excluded from backup, never exportable (admin is not a bypass), and forbidden as did:webvh update keys — for cases where "this key can never leave this VTA" matters more than "this key can be recovered from the mnemonic"; and **imported** Ed25519 keys, for a deterministic did:key that must match a key created elsewhere. The derivation code itself also moved in-tree (SLIP-0010, dropping the `ed25519-dalek-bip32` dependency) when the workspace moved to curve25519-dalek 5. Post-quantum *internal* keys are refused for now: whether a VTA should hold an unrecoverable ML-DSA key is a decision not yet taken. -Derivation from one seed is the rule, but the [[verifiable-trust-agent|VTA]] now deliberately holds two kinds of key *outside* the tree: **non-extractable internal signing keys** — generated from a CSPRNG with no derivation path, stored in their own keyspace that is excluded from backup, never exportable (admin is not a bypass), and forbidden as did:webvh update keys — for cases where "this key can never leave this VTA" matters more than "this key can be recovered from the mnemonic"; and **imported** Ed25519 keys, for a deterministic did:key that must match a key created elsewhere. The derivation code itself also moved in-tree (SLIP-0010, dropping the `ed25519-dalek-bip32` dependency) when the workspace moved to curve25519-dalek 5. +A third note cuts the other way: "recoverable from the mnemonic" is not the same as "exportable". Since September 2026 any key can be marked **`exportable: false`** so it can only ever be *used* through the signing oracle, never read out (#1401, #1407), and the misnamed, unspecced, global-admin-gated `seeds/export-mnemonic` was retired in favour of **`keys/export-secret`**, which exports one named key and respects that flag (#1404). The seed still backs everything up; an operator simply no longer has a blanket way to pull key material out of a running VTA. -See also: [[verifiable-trust-agent]], [[decentralized-identifiers]] +See also: [[verifiable-trust-agent]], [[decentralized-identifiers]], [[post-quantum-cryptography]] diff --git a/content/concepts/coordinated-releases.md b/content/concepts/coordinated-releases.md index 883e6d9..9c8d54f 100644 --- a/content/concepts/coordinated-releases.md +++ b/content/concepts/coordinated-releases.md @@ -1,14 +1,14 @@ --- -title: "Coordinated Releases — Aspen, Banyan, Cypress" +title: "Coordinated Releases — Aspen, Banyan, Cypress, Dogwood, Eucalyptus" type: concept -tags: [release, openvtc, vti, milestones, cypress] -date-updated: 2026-08-19 -sources: [verifiable-trust-infrastructure, openvtc, dtg-credentials, affinidi-tdk, affinidi-webvh-service, vti-setup, verifiable-git-infrastructure] +tags: [release, openvtc, vti, milestones, cypress, dogwood, eucalyptus] +date-updated: 2026-09-18 +sources: [verifiable-trust-infrastructure, openvtc, dtg-credentials, affinidi-tdk, affinidi-webvh-service, vti-setup, verifiable-git-infrastructure, vta-browser-plugin, vti-didcomm-js, rp-sdk-js] --- -# Coordinated Releases — Aspen, Banyan, Cypress +# Coordinated Releases — Aspen, Banyan, Cypress, Dogwood, Eucalyptus -The OpenVTC ecosystem is a dozen fast-moving repositories — the [[verifiable-trust-infrastructure|VTI]] alone has merged 300+ PRs a month through mid-2026, and its crates are consumed by eight sibling repos. Individual crate versions tell you very little about whether *the stack as a whole* works together. The answer, since June 2026, is a **coordinated release**: a tree-named, alphabetical milestone tag applied across every participating repository at a moment when the whole stack has been exercised end to end. The tag is the thing the [[vti-setup]] guides pin to, the thing a newcomer should check out, and the thing the team means when it says "runs on Cypress." +The OpenVTC ecosystem is a dozen fast-moving repositories — the [[verifiable-trust-infrastructure|VTI]] alone merged more than 500 commits in the single month after Cypress, and its crates are consumed by eight sibling repos. Individual crate versions tell you very little about whether *the stack as a whole* works together. The answer, since June 2026, is a **coordinated release**: a tree-named, alphabetical milestone tag applied across every participating repository at a moment when the whole stack has been exercised end to end. The tag is the thing the [[vti-setup]] guides pin to, the thing a newcomer should check out, and the thing the team means when it says "runs on Dogwood." ## The Sequence @@ -16,29 +16,70 @@ The OpenVTC ecosystem is a dozen fast-moving repositories — the [[verifiable-t |---------|------|--------------|----------------| | **`openvtc-aspen`** | 2026-06-03/04 | VTI, did-hosting-service | The first cross-repo snapshot, named for OpenVTC; preceded the multi-community work | | **`Banyan`** | 2026-06-22 | VTI, openvtc, dtg-credentials, vti-setup | The multi-community milestone — OpenVTC's T1–T9 complete, reciprocal VMCs, the VTI's P0–P3 security campaign; a lightweight tag, no RCs | -| **`Cypress`** | **2026-08-17** | VTI, openvtc, dtg-credentials, TDK, did-hosting-service, VGI (+ vti-setup docs pinned to it) | The first release with formal **release candidates** (`VTI-Cypress-RC-0` 2026-07-30 → 08-02, `VTI-Cypress-RC-1` 2026-08-10/11) and the first cut as a crates.io-published snapshot under the VTI's new release-plz process | +| **`Cypress`** | 2026-08-17 | VTI, openvtc, dtg-credentials, TDK, did-hosting-service, VGI, browser plugin, vti-didcomm-js, rp-sdk-js (+ vti-setup docs pinned to it) | The first release with formal **release candidates** (`VTI-Cypress-RC-0` 2026-07-30 → 08-02, `VTI-Cypress-RC-1` 2026-08-10/11) and the first cut as a crates.io-published snapshot under the VTI's new release-plz process | +| **`VTI-Dogwood`** | **2026-08-30** (browser plugin 08-31) | all nine of the above (+ vti-setup explore docs pinned to it) | The **silent release**: tag-only, no GitHub Release, no announcement. One RC (`VTI-Dogwood-RC-1`, 2026-08-22 → 08-29). The Cypress → Dogwood window was about making the wire honest rather than adding features — see below | +| **`VTI-Dogwood-R1`** | 2026-08-30 → 09-01 | the same nine | A **re-cut** of Dogwood carrying production fixes found in the days after the tag. In five repos R1 is the identical commit; in the VTI, TDK, openvtc and browser plugin it adds a handful of fixes each (details below) | +| **`VTI-Eucalyptus-RC-0`** | 2026-09-17 | VTI, openvtc, TDK, did-hosting-service (so far) | **In flight.** The first release candidate of the next release: TSP Rev 3, data rooms, post-quantum keys, the persona store, the SEC-4045 hardening sweep | -Each tag carries the same one-line description: *"Coordinated OpenVTC release — a known-good, cross-project snapshot of all OpenVTC projects. Tagged at the main-branch HEAD on ."* Note what it is **not**: not a semver bump (every crate keeps its own version), not a freeze (main moved on the next day), and not a CHANGELOG release in every repo (did-hosting-service's Cypress tag sits on work its CHANGELOG still lists as *Unreleased*). It is a coordination point. +Each tag carries a one-line annotation (`VTI Dogwood`, `VTI Dogwood R1`, `VTI Eucalyptus RC-0`). Note what a coordinated release is **not**: not a semver bump (every crate keeps its own version), not a freeze (main moved on the next day), and not a CHANGELOG release in every repo (did-hosting-service has not bumped a single crate version since Cypress, yet carries all three new tags). It is a coordination point. Since Dogwood, the tag naming has settled on the `VTI-` / `VTI--RC-` / `VTI--R` convention across all repos. -## What Cypress Snapshots +## Dogwood: the silent release -| Repo | At the `Cypress` tag | -|------|----------------------| -| [[verifiable-trust-infrastructure\|VTI]] | vta-service **0.17.0**, vta-sdk **0.25.0**, vtc-service 0.11.58, vti-common 0.12.1, vti-secrets 0.1.14, pnm-cli 0.12.6, cnm-cli 0.11.22, vtc-client 0.3.7, vta-mobile-core 0.6.18, vta-enclave 0.7.7, vta-mcp 0.1.5 (+ the eleven subsystem crates — full table on the entity page) | -| [[openvtc\|OpenVTC]] | workspace **0.3.1** (two commits past the **v0.3.0** release of 2026-08-15), trust-tasks 0.9 / vta-sdk 0.25 / did-git-sign 0.4.5 | -| [[dtg-credentials]] | **0.2.0** — tracks the DTG Core Credentials spec v1.0 WD01 | -| [[affinidi-tdk\|Affinidi TDK]] | mediator **0.18.19**, messaging-sdk **0.19.8**, messaging-delivery 0.1.14, didcomm-v1 0.2.0, tdk-common 0.6.7, cache-server 0.9.10, agent-names 0.1.3, `affinidi-tdk` 0.8.5 | -| [[affinidi-webvh-service\|did-hosting-service]] | server / daemon / watcher / witness **0.8.3**, control **0.8.8**, common 0.8.6, client 0.1.2, webvh-ui 1.1.0 | -| [[verifiable-git-infrastructure\|VGI]] | all crates **0.4.5** | -| Shared stack | trust-tasks-rs **0.9**, affinidi-did-common 0.4, [[didwebvh-rs]] 0.6.0, curve25519-dalek 5 | -| [[vti-setup]] | guides re-verified against the above; explore source checkouts `git checkout Cypress`; pre-built binaries under `download.firstperson.dev//latest/` | +Dogwood was deliberately unannounced. Looking at what changed between Cypress and Dogwood explains why: almost none of it is user-visible, and almost all of it is the kind of work that makes the *next* features possible. + +- **The wire became honest.** The VTI ran a Trust-Task conformance sweep — real handler responses validated against the published schemas — and drove the VTC's schema drift from 33 to 0 in two days, then enforced Trust-Task framework 0.5.0 at the dispatch spine (`recipient`, `proof`, audience and `issuedAt` checked per spec). Every client now has an identity and signs; any DID that names a key may sign (previously did:key only, which had locked provisioned did:webvh integrations out of every proof-requiring task). The browser wallet discovered the consequence the hard way: with spec checks on, 93 of 141 task types required a proof and none of its calls carried one, so it now signs every outbound Trust Task at the channel. +- **The membership edge finally closed.** The member→community half of the VMC pair had *never actually landed* in OpenVTC — VMCs had no `id` field and the VTC's rejection was being dropped as uncorrelated. dtg-credentials 0.3.0 → 0.5.0 (three breaking releases in two days), OpenVTC #259–#267 and the VTI's half-edge / complete-edge distinction fixed it; the Dogwood tag in the VTI sits on the commit that draws the membership edge on the graph. +- **Identity and secrets stopped being fragile.** OpenVTC's Linux secrets had lived in the RAM-only kernel keyring and vanished on reboot; pairwise R-DIDs became the default and VRCs are issued under them; VGI moved the commit's identity claim out of `user.email` and into a `Signed-by-DID:` trailer; the TDK gained did:webs and did:scid resolution. +- **Retries, idempotency, replay and freshness** got first-class treatment across the VTA (`idempotencyKey` dedup, `ReplayGuard`, `FreshnessPolicy`, error messages that no longer act as a probing oracle). +- **Dependencies moved a long way**: trust-tasks-rs 0.9 → 0.17, affinidi-tdk 0.8 → 0.10, vta-sdk 0.25 → 0.32 — the reason every downstream repo needed the same tag. + +## Why Dogwood-R1 exists + +The R1 tags are the answer to the same question in each repo: what broke in production within 48 hours of Dogwood? + +| Repo | What R1 adds over Dogwood | +|------|---------------------------| +| [[affinidi-tdk\|TDK]] | Blind cross-mediator relay refused every peer-relayed message as a session mismatch, so **no federated delivery worked**; the forwarding-abandonment problem report was plaintext (discarded by authcrypt-only clients); TSP forwarding failed when the next hop's endpoint is a mediator DID (#748–#751) | +| [[openvtc\|OpenVTC]] | TSP routed sends passed the *peer's* mediator as the first hop — only our own mediator can unwrap the outer layer, so every cross-mediator join failed with a 404 from our own side (#273); a phantom "setting saved" in Settings (#274) | +| [[vta-browser-plugin\|browser wallet]] | The wallet had one wallet-wide inbox, so a multi-VTA operator found that **every agent but one silently lost its consent prompts** (#148–#151); granted-notices parsed at the wrong envelope shape so approvals never auto-published (#153); a third consent prompt for an already-approved payload (#154) | +| [[verifiable-trust-infrastructure\|VTI]] | One release PR (#1218): device list/disable/wipe scoped to the caller's contexts, typed not-found/conflict/gone across the Trust-Task boundary, provisioning checks authorization before minting, uniffi 0.32 / tdk 0.11 / mdoc 0.3 | +| did-hosting-service, dtg-credentials, VGI, vti-didcomm-js, rp-sdk-js | Identical commit to Dogwood | + +## What Dogwood Snapshots + +| Repo | Cypress | `VTI-Dogwood` | `VTI-Dogwood-R1` | `VTI-Eucalyptus-RC-0` | +|------|---------|---------------|------------------|-----------------------| +| [[verifiable-trust-infrastructure\|VTI]] vta-service / vta-sdk / pnm-cli / cnm-cli / vtc-client | 0.17.0 / 0.25.0 / 0.12.6 / 0.11.22 / 0.3.7 | **0.23.3 / 0.32.2** / 0.14.2 / 0.13.2 / 0.5.1 | 0.23.4 / 0.32.3 / 0.14.3 / 0.13.3 / 0.5.1 | **0.33.0 / 0.42.1** / 0.17.2 / 0.16.3 / 0.6.8 (+ vta-persona 0.3.9, vti-rooms 0.2.9) | +| [[openvtc\|OpenVTC]] workspace (vta-sdk / dtg-credentials it builds against) | 0.3.1 (0.25 / 0.2.0) | 0.3.1 (0.32.2 / 0.5.0) | 0.3.1 (0.32.2 / 0.5.0) | 0.3.1 (0.42.1 / 0.9.1) | +| [[dtg-credentials]] | 0.2.0 (WD01) | **0.5.0** | 0.5.0 | 0.9.1 on main (WD02) — not yet tagged | +| [[affinidi-tdk\|TDK]] mediator / messaging-sdk / didcomm-service / affinidi-tsp / facade | 0.18.19 / 0.19.8 / 0.3.26 / 0.1.14 / 0.8.5 | **0.20.3 / 0.21.0** / 0.5.0 / 0.1.14 / 0.10.0 | 0.20.6 / 0.21.1 / 0.5.1 / 0.1.15 / 0.11.0 | **0.26.2 / 0.26.7** / 0.11.0 / **0.2.1** / 0.16.0 | +| [[affinidi-webvh-service\|did-hosting-service]] server / daemon / control | 0.8.3 / 0.8.3 / 0.8.8 | unchanged | unchanged | unchanged (consumes trust-tasks 0.21 / vta-sdk 0.41) | +| [[verifiable-git-infrastructure\|VGI]] | 0.4.5 | **0.4.7** (RC-1 = 0.4.6) | 0.4.7 | 0.4.12 on main — not yet tagged | +| [[vta-browser-plugin\|browser wallet]] pnm-core / vti-tsp-js | 0.4.0 / 0.2.0 | **0.6.0** / 0.2.0 | 0.6.0 / 0.2.0 | 0.9.1 / 0.3.0 on main — not yet tagged | +| [[vti-didcomm-js]] | 0.6.2 | **0.7.0** | 0.7.0 | 0.10.1 on main — not yet tagged | +| [[rp-sdk-js]] | 0.2.0 | 0.2.0 | 0.2.0 | 0.2.0 (unreleased hardening on main) | +| Shared stack | trust-tasks-rs 0.9, [[didwebvh-rs]] 0.6.0 | trust-tasks-rs **0.17**, didwebvh-rs 0.6.0 | same | trust-tasks-rs **0.21.3**, didwebvh-rs **0.7.0** | +| [[vti-setup]] explore stream | `git checkout Cypress`; VTA 0.17.0 / mediator 0.18.19 / DHD 0.8.3 / VTC 0.11.58 | `git checkout VTI-Dogwood`; VTA 0.23.2 / mediator 0.20.2 / DHD 0.8.3 / VTC 0.11.58 | same | not yet re-pinned | + +The full per-crate tables live on each entity page. + +## Eucalyptus, so far + +`VTI-Eucalyptus-RC-0` was tagged on 2026-09-17 in the VTI, OpenVTC, the TDK and did-hosting-service. It is the opposite of Dogwood in character — the largest feature window the ecosystem has had: + +- **TSP Rev 3** as a flag day across the TDK, VTI, did-hosting and OpenVTC (with the browser stack dual-reading Rev 2) — see [[trust-spanning-protocol]]. +- **[[data-rooms|Data rooms]]** — credential-governed, MLS-encrypted shared spaces with their own DIDs, four new VTI crates and a `room-host` binary. +- **[[post-quantum-cryptography|Post-quantum keys]]** in the VTA (ML-DSA-44/65, BIP-32-derived, did-templates 3.0) and hybrid multi-proof credentials from the VTC. +- **The persona store** (`vta-persona`, the VTA's fourth holder store) and OpenVTC's *My Identity* pane; the start of [[peer-identity-vetting]]. +- **Spec Working Draft 02** of the [[dtg-credential-spec|DTG Core Credentials]] and dtg-credentials 0.9.1 — [[correlation-scope]] replacing the DID-type taxonomy, [[authority-credential|Authority]] and [[delegation-credential|Delegation]] credentials, `digestMultibase`. +- **The SEC-4045 hardening sweep** (2026-09-10 → 09-12) across every repo: public-hosts-only DID resolution, egress guards in the mediator and the browser DIDComm stack, hash-chained VTA audit log, SHA-pinned CI, secrets never in environment overrides. ## Why It Matters -- **For operators and newcomers**: Cypress is the answer to "which versions go together?" — the vti-setup walkthroughs, the VTA Farm, and the `latest` download aliases all track it. -- **For the team**: the RC process forced the cross-repo dependency graph into shape — the run-up to Cypress is where the VTI dropped its `[patch.crates-io] vta-sdk` self-pin, OpenVTC and VGI converged on "one vta-sdk in the binary," and the TDK pinned the mediator to vta-sdk 0.25. -- **For the wiki**: the tags are the reference points the entity activity logs are organised around; each entity page notes its own Cypress versions. +- **For operators and newcomers**: the latest tag is the answer to "which versions go together?" — the vti-setup explore walkthrough and the `download.firstperson.dev//latest/` binaries track **Dogwood**. (The managed [[vtafarm|VTA Farm]] is the exception: it offers whatever GHCR image tags exist, newest first, rather than pinning a coordinated release.) +- **For the team**: the RC process forces the cross-repo dependency graph into shape. Cypress is where the VTI dropped its `[patch.crates-io]` self-pin; Dogwood is where every downstream repo absorbed trust-tasks 0.17 and vta-sdk 0.32; the Eucalyptus run-up is where OpenVTC deleted its last 22 `[patch.crates-io]` entries and the whole stack moved to TSP Rev 3 at once. +- **For the wiki**: the tags are the reference points the entity activity logs are organised around; each entity page notes its own versions at each tag. -The next release will presumably take the next tree name in the alphabet. +The next tree name after Eucalyptus has not been chosen. See also: [[overview]], [[verifiable-trust-infrastructure]], [[openvtc]], [[vti-setup]] diff --git a/content/concepts/correlation-scope.md b/content/concepts/correlation-scope.md new file mode 100644 index 0000000..bfc89b5 --- /dev/null +++ b/content/concepts/correlation-scope.md @@ -0,0 +1,51 @@ +--- +title: "Correlation Scope — pairwise, directed, public" +type: concept +tags: [identity, privacy, did, correlation, dtg, spec] +date-updated: 2026-09-18 +sources: [dtg-credential-spec, dtg-credentials, openvtc] +--- + +# Correlation Scope — pairwise, directed, public + +Working Draft 02 of the [[dtg-credential-spec|DTG Core Credentials spec]] (PR #30, 2026-09-05) retired the four-way DID taxonomy — R-DID, M-DID, C-DID, P-DID — and replaced it with a single question the *holder* answers about each identifier: **how widely do I intend this identifier to be correlated?** The answer is a **correlation scope**, one of three ordered values. + +## Why the DID types had to go + +Each old type packed two independent facts into one name: *what the identifier is attached to* (a relationship, a membership, a community, a persona) and *how widely it may be correlated*. The two disagreed as soon as an identifier did two jobs — and the spec explicitly allowed an M-DID inside a VRC, at which point the same identifier answered to two names at once. + +The first fact was always redundant. An identifier is not "a membership identifier"; it is an identifier that *has* a [[membership-credential|VMC]]. It is not "a persona identifier"; it is one that *has* a [[persona-credential|VPC]]. Only the correlation width was unrepresented — and it is the one thing a person has to decide and a verifier has to respect. So the spec kept that axis and dropped the rest: **roles are conferred by credentials; scope is declared by the holder.** The four glossary entries were deleted outright. See [[did-types]] for the history. + +## The three values + +| Scope | Known to | What the holder intends | +|---|---|---| +| **`pairwise`** | exactly one counterparty | correlation confined to this one relationship | +| **`directed`** | a set of counterparties the holder chooses | deliberate correlation across that set and no further | +| **`public`** | anyone | correlation unbounded; ordinarily published so it can be found | + +The values are ordered narrowest-first. Where the spec states a minimum scope for a purpose, a narrower declaration does not satisfy it: a [[witness-credential|VWC]] witness is `directed` at minimum, and a [[verifiable-trust-community|VTC]]'s own identifier can only truthfully be `public` — "a community that cannot be found cannot be joined." Elsewhere the holder may declare any of the three, and a verifier MUST NOT infer scope from an identifier's value, its DID method, or where it was encountered. + +Three values, not four. A "community-bounded" value was proposed and rejected: its bound would come from a credential rather than from the holder — the very conflation being removed — and no verifier could tell it apart from `directed`. An identifier used only with the community is `pairwise`; one also used with fellow members is `directed`. The join-time choice is three-way: *a one-time pseudonym, a private persona, or a public persona.* + +`pairwise` is not "disposable". An identifier used only toward a VTC must stay verifiable for the life of the membership, so it needs key history and rotation while avoiding a shared resolution origin. Durability and scope are independent axes — which is why the spec expects deployments to mix DID methods ([[decentralized-identifiers]], [[did-webvh]]). + +## What a declaration cannot do alone + +A declared scope binds only its holder's disclosure, not what a counterparty does with the identifier — and for a community that gap needs governance. A VTC that publishes a member directory, or hands a member-issued VMC to a third party, widens an identifier the member declared `pairwise` without the member choosing it. So WD02 adds a duty: **a VTC issuing VMCs MUST publish, in its governance framework or trust registry, whether member identifiers are disclosed beyond the VTA and to whom**; a verifier MUST NOT infer from a `pairwise` declaration that the community honours it; and a VTA SHOULD show the community's answer to a prospect *before* they choose a scope ([[trust-registries]]). + +The same logic gives the **effective disclosure of an edge** (PR #27): each half of a [[decentralized-trust-graph|DTG edge]] is issued under an identifier its own party chose, so an edge's disclosure is the *wider* of its two halves. A correctly pairwise half is still correlated to a named party if the counterparty published the opposing half under a `directed` or `public` identifier. Compute an edge's privacy over both halves; never present a pairwise half as making the edge pairwise. + +## Consequences for proofs + +Under strict `pairwise`, the identifier a member uses toward their VTC and the one they use toward a fellow member differ *by construction*. A community-anchored [[zero-knowledge-proofs|ZKP]] can no longer read one identifier out of both the VMC and the VRC; it must prove **common control** of two distinct identifiers — a primitive no DTG spec yet defines (spec issue #9, with the ZKP task force). Members expecting to prove community-anchored relationships will in practice declare `directed` for intra-community use. Where a holder deliberately reuses one `directed` or `public` identifier, the correlation is on the face of the credentials and needs no proof. + +## What is not yet decided + +**The property that carries the declaration has not been named.** The spec settled *where* it goes — in the credential, declared by each issuer for its own identifier, since `did:key` and `did:peer` documents are derived from the identifier and have nowhere to hold a property — but the property and its `@context` term are open. Until then the rules bind a declaration that has been made and require none: no credential is non-conforming for lacking one, and in a bidirectional edge the subject's scope is undeclared until the reciprocal half exists. The [[dtg-credentials]] crate implements nothing for scope beyond dropping the retired names from its docs. + +## In practice + +[[openvtc|OpenVTC]] already behaves as the model expects: since Dogwood (openvtc #254/#255, on VTI #1061) a pairwise relationship identifier is the default and the [[relationship-credential|VRC]] is issued under it — a `pairwise`-scope identifier per relationship in WD02 terms — while the persona identifier a member joins with is what WD02 would call `directed` or `public`. What is missing everywhere is the declaration itself. + +See also: [[did-types]], [[persona-credential]], [[relationship-credential]], [[membership-credential]], [[zero-knowledge-proofs]], [[decentralized-identifiers]] diff --git a/content/concepts/credential-categories.md b/content/concepts/credential-categories.md index b92bf6e..af3d207 100644 --- a/content/concepts/credential-categories.md +++ b/content/concepts/credential-categories.md @@ -2,65 +2,72 @@ title: "DTG Credential Categories" type: concept tags: [credentials, dtg, taxonomy, categories] -date-updated: 2026-08-19 -sources: [dtg-credential-spec] +date-updated: 2026-09-18 +sources: [dtg-credential-spec, dtg-credentials] --- # DTG Credential Categories -The [[dtg-credential-spec|DTG Core Credentials specification]] (v1.0 Working Draft 01) organizes its six credential types into three **descriptive** (non-normative) functional categories. These categories don't appear in credential schemas — they're a conceptual framework for understanding what each credential type does in the [[decentralized-trust-graph|trust graph]]. +The [[dtg-credential-spec|DTG Core Credentials specification]] groups its credential types into **descriptive** (non-normative) functional categories. These never appear in credential schemas — the formal type hierarchy has exactly one abstract parent, `DTGCredential` — but they are the quickest way to understand what each credential does to the [[decentralized-trust-graph|trust graph]]: does it *form an edge*, *annotate one*, or neither? -## The Three Categories +The categorisation moved twice in 2026, so this page states both the tagged and the in-progress views: -### Edge Credentials +- **Working Draft 02** (tagged 2026-09-07; what [[dtg-credentials]] 0.7+ implements): **eight types**, three categories, one outsider. +- **`main`, heading for Working Draft 0.4.0** (as of 2026-09-15): **seven types**, two categories, two outsiders. -Establish relationships between existing entities (nodes) in the graph. Both edge credential types are **bidirectional** — a complete edge requires a pair, one from each side. +## Edge Credentials — form graph structure -- **[[membership-credential|VMC (Membership)]]** — a membership edge between an entity and a community, verified through a bi-directional pair of VMCs -- **[[relationship-credential|VRC (Relationship)]]** — a peer-to-peer relationship edge between two entities, verified through a bi-directional pair of VRCs +Establish relationships between existing entities (nodes). Every edge credential is **bidirectional**: a complete edge needs a pair, one from each side, and the second half is the counterparty's consent. -### Invitation Credentials +- **[[membership-credential|VMC (Membership)]]** — a community-issued *grant* plus a member-issued *acknowledgement* carrying a digest of the grant +- **[[relationship-credential|VRC (Relationship)]]** — one VRC from each peer +- **[[delegation-credential|VDC (Delegation)]]** *(new in WD02)* — a delegator's *grant* plus the delegate's required *acceptance*; establishes that one party may act in another's name -Bootstrap new participants into communities. +## Annotation Credentials — attach data to existing structure -- **[[invitation-credential|VIC (Invitation)]]** — authorizes onboarding of a new member into a VTC or VTN +Create no graph structure; annotate edges or parties that already exist. -### Annotation Credentials +- **[[persona-credential|VPC (Persona)]]** — links a persona identity to an existing relationship +- **[[endorsement-credential|VEC (Endorsement)]]** and **[[witness-credential|VWC (Witness)]]** — in WD02, two concrete types (`EndorsementCredential`, `WitnessCredential`). On `main` both become **predicate profiles** of a single **[[statement-credential|VSC (Statement)]]** type, `dtg:endorses` and `dtg:witnessed`; the type strings are removed and the VSC is the general "signed statement by one node about another" -Attach additional data to existing graph structure without creating new edges. +## Outside the categories -- **[[persona-credential|VPC (Persona)]]** — links a persona identity to an existing relationship -- **[[endorsement-credential|VEC (Endorsement)]]** — endorses skills or reputation of an existing member -- **[[witness-credential|VWC (Witness)]]** — third-party attestation that a specific edge (VRC) was established in a specific exchange; one VWC per direction +- **[[invitation-credential|VIC (Invitation)]]** — in WD02 it was the sole member of an "Invitation Credentials" category. On `main` (2026-09-10, closing spec issue #28) it was promoted to a top-level section: a category with one member is the structural problem #28 exists to remove. The VIC bootstraps a node *into* a community; it neither forms an edge nor annotates one. +- **[[authority-credential|VAC (Authority)]]** *(new in WD02)* — confers permission to act within a named scope and may be attenuated by its holder. Placed outside every category from the start, for the same one-member reason, and because it is different in kind: it *confers* rather than *attests*. + +So `main`'s picture is **two categories — Edge (VMC, VRC, VDC) and Annotation (VPC, VSC) — with the VIC and VAC standing apart**, the VSC carrying the VEC and VWC as profiles. + +## Statements versus establishments -### What happened to the fourth category? +`main` also introduces a sharper test than the categories, and the VSC is its consequence. A credential is a **statement** if verifying it means checking the signature, checking status and reading the claim; it is an **establishment** if the verifier must do more — complete an edge from two halves, match a consent digest, resolve a chain, contain a scope, or hand the credential to a policy enforcement point. Statements share one type (the VSC) and differ only in predicate; establishments (VRC, VMC, VDC, VAC, VIC) each keep a concrete subtype. The VPC is statement-shaped but keeps its own type because its issuer *is* the persona identifier. -Until July 2026 the spec (v0.3) listed a fourth category, **Verifiable Data Structures (VDS)**, containing the **RCard / relationship card** — human-readable contact information (vCard/jCard per RFC 7095) in a verifiable wrapper, typed `["VerifiableCredential", "RelationshipCard"]` *without* the `DTGCredential` parent. Working Draft 01 removed it from this spec: the relationship card is a VDS ("a data structure digitally signed by the publisher so that subscribers can verify the original and any updates — a VC is one kind of VDS, an r-card is another"), not a DTG credential, and will be defined in a planned companion, **DTG Core Verifiable Data Structures**, alongside an *agent card* modelled on the A2A protocol's AgentCard. The [[dtg-credentials]] crate deprecated (but kept) its RCard types in 0.2.0. +## What happened to the fourth category? + +Until July 2026 the spec (v0.3) listed **Verifiable Data Structures (VDS)** containing the **RCard / relationship card** — vCard/jCard contact data (RFC 7095) in a verifiable wrapper, typed `["VerifiableCredential", "RelationshipCard"]` *without* the `DTGCredential` parent. Working Draft 01 removed it: an r-card is a VDS ("a data structure digitally signed by the publisher so that subscribers can verify the original and any updates — a VC is one kind of VDS, an r-card is another"), not a DTG credential, and will be defined in the planned **DTG Verifiable Data Structures** companion alongside an *agent card* modelled on A2A's AgentCard. The [[dtg-credentials]] crate keeps deprecated RCard types. ## The Formal Type Hierarchy -All DTG credentials share a common W3C type hierarchy: +**WD02** (what implementations use today): ``` VerifiableCredential └── DTGCredential ├── MembershipCredential (VMC) ├── RelationshipCredential (VRC) + ├── DelegationCredential (VDC) ├── InvitationCredential (VIC) ├── PersonaCredential (VPC) ├── EndorsementCredential (VEC) - └── WitnessCredential (VWC) + ├── WitnessCredential (VWC) + └── AuthorityCredential (VAC) ``` -The only abstract parent in the formal hierarchy is `DTGCredential`. The three descriptive categories above are for human understanding only. Every type may also carry a `taskContext` binding it to the trust-task exchange that produced it — see [[trust-task-context-binding]]. - -## ZKP Anchor Points +**`main` / WD 0.4.0**: the same, with `EndorsementCredential` and `WitnessCredential` replaced by a single `StatementCredential (VSC)` whose `predicate` selects the profile. -Both Edge Credentials are anchors for [[zero-knowledge-proofs|ZKP constructions]] defined in the spec: +Every type may carry a `taskContext` binding it to the trust-task exchange that produced it — see [[trust-task-context-binding]]. Four credentials reference another by digest (VMC acknowledgement, VWC, VDC `parent`/`accepts`, VAC `parent`), all using one **`digestMultibase`** encoding defined in WD02. -- The **VRC** anchors a **pairwise ZKP** — available between any two VRC holders, no shared community required, useful for selectively disclosing P-DIDs while hiding R-DIDs. -- The **VMC** anchors a **community-anchored ZKP** — requires both parties to hold VMCs from the same community, carries that community's governance assurance (including personhood when applicable) into the proof. +## ZKP Anchor Points -The spec recommends ZKP presentation by default for all DTG credentials. +The two Edge Credentials that existed first remain the anchors for the [[zero-knowledge-proofs|ZKP constructions]] the spec defines: the **VRC** anchors the **pairwise ZKP** (any two VRC holders; discloses `directed` persona identifiers while hiding `pairwise` ones), the **VMC** anchors the **community-anchored ZKP** (both parties hold VMCs from the same community, whose assurances — including personhood — carry into the proof). WD02 adds chain-validity predicates for VDC and VAC chains, a shared-subject predicate for membership-plus-authority, and, on `main`, a single predicate shape for all VSC profiles. The spec recommends ZKP presentation by default. -See also: [[zero-knowledge-proofs]], [[dtg-credentials-overview]], [[decentralized-trust-graph]] +See also: [[zero-knowledge-proofs]], [[dtg-credentials-overview]], [[decentralized-trust-graph]], [[authority-credential]], [[delegation-credential]], [[statement-credential]] diff --git a/content/concepts/data-rooms.md b/content/concepts/data-rooms.md new file mode 100644 index 0000000..e31d758 --- /dev/null +++ b/content/concepts/data-rooms.md @@ -0,0 +1,37 @@ +--- +title: "Data Rooms" +type: concept +tags: [rooms, data-rooms, mls, shared-memory, agents, dtg-credentials, vac, portability, host-neutral, browser] +date-updated: 2026-09-18 +sources: [verifiable-trust-infrastructure, dtg-credentials, dtg-credential-spec, vta-browser-plugin] +--- + +# Data Rooms + +A **data room** is a shared, credential-governed, end-to-end-encryptable space — a set of records readable and writable by exactly the parties the *room itself* admits. It is the first construct in the ecosystem that is neither a person nor a community: a room has its own DID, issues its own [[dtg-credentials-overview|DTG credentials]], and can move from one host to another without a single credential being reissued. Rooms landed in the [[verifiable-trust-infrastructure|VTI]] between 2026-09-03 and 2026-09-17 and are the headline of the `Eucalyptus` release candidate ([[coordinated-releases]]). + +## Why rooms exist + +The motivating case is **shared memory for AI agents**. A personal agent already has private memory in its holder's [[verifiable-trust-agent|VTA]] (`vta/memory/*`). What was missing was a space an agent can *also* recall from and write to, whose access control belongs to the *people* in it — a team's shared context, a working group's notes, a family's records. The design note is explicit that a room serves two audiences at once: humans reading and curating it as shared information, and their agents recalling from it as shared memory. One record model, never a human wiki beside an agent store. + +The second motivation is the one that shaped the architecture: **the host must not be trusted with anything but availability.** A community operator running a VTC is the worst-placed party to read its members' shared material, and an encrypted-at-rest store that still keeps a member list has quietly handed that operator the membership. So the design inverts the assumption most storage is built on: *there is no member list, and there must not be one.* Every operation is authorized by a presentation of credentials the room issued, verified against the room's own DID — never by a host ACL, a session, or a roster. A host that kept a roster would become part of the room's membership, and the room could no longer leave it. The absence of the roster is what makes a room portable. + +## How a room works + +**The room is a DTG node.** It has a DID (`did:webvh`, ideally witnessed — see [[did-webvh]]; `did:peer` and `did:key` rooms also work for the browser demo). Its identifier *is* the `roomId`. Because it is a node in the [[decentralized-trust-graph]], the existing credential vocabulary applies one level down: a **VIC** ([[invitation-credential]]) to enter once (joining is consent — a two-party act), a **VMC** ([[membership-credential]]) to prove membership thereafter, and one new type, the **Verifiable Authority Credential (VAC)**, for what a member may *do* — `read`, `write`, `curate`, `admin`. The VAC is attenuable: a member's VTA derives a narrower, hours-scoped, single-action grant for their agent without going back to the issuer. VACs (and a companion VDC for delegation) were added to [[dtg-credentials]] in September 2026 and to the [[dtg-credential-spec|spec]] as Working Draft 02. + +**Four topologies, one protocol.** T1 *Personal* (a room on my own host, anyone I invite), T2 *Community* (a room on a [[verifiable-trust-community|VTC]], governed by the community's `rooms.rego`), T3 *Cross-community* (one home host, members from several communities — free once T2 exists, because a VMC binds a member to the room, not to a community), and T4 *Peer* (two [[relationship-credential|VRC]]-linked peers). What differs is where the room lives and who governs creation, never what a room is. This is why the Trust-Task family is top-level `rooms/*`, not `vtc/rooms/*`. + +**Three visibility tiers, fixed at creation.** `open` (cleartext, server-searchable), `attributed` (bodies encrypted; the host still sees which member acted), and `private` (encrypted, membership proven by an unlinkable [[zero-knowledge-proofs|zero-knowledge]] presentation). The ladder measures *what the host can see*. As of Eucalyptus RC-0, `open` and `attributed` serve; `private` is stored but refused until the DTG working group settles the same-subject ZK profile. + +**MLS is the group layer.** On the sealed tiers, records are encrypted under keys derived from an MLS (RFC 9420) group. The mapping is exact: the DTG is MLS's Authentication Service, the host is its Delivery Service (trusted for availability only), a membership change is a Commit made only by the owner, and the invitation carries the Welcome. One leaf per member — and the leaf is the member's **VTA**, not their devices. The VTA acts as a decryption-and-proving oracle: an agent sends ciphertext and gets plaintext, asks for a presentation and gets an attenuated one; nothing key-shaped ever crosses. An *epoch key chain* lets a member (including one who has just joined) read records sealed under earlier epochs; the rungs are ciphertext the host stores but cannot open. + +**Hosts are interchangeable.** Two hosts implement `rooms/*` today: `vtc-service` in-process (T2/T3) and the standalone **`room-host`** binary (T1/T4) — a deliberately tiny service with no policy engine, no roster and no admin surface, which stores ciphertext and answers Trust Tasks. It serves over HTTP, or over [[didcomm|DIDComm]] and [[trust-spanning-protocol|TSP]] via a mediator (connecting as its own `did:peer:2`, so a host on a laptop behind NAT is reachable), with identical authorization on every carrier because the presenter comes from the document's own proof. It can run as a **read mirror** of another host's room, pulling as an ordinary member with a `read` chain. Its identity can be enrolled with and governed by a VTA, following the same provisioning pattern as mediators and DID hosts ([[vta-topology]]). + +**The host is kept honest, not trusted.** Records are signed and AEAD-bound to `(roomId, key, version, epoch)`, so a host cannot forge, alter or relocate one. Silence is the remaining attack, so hosts compute a Merkle **data commitment** over a room's records and serve inclusion traces, and the owner periodically **anchors** the MLS epoch authenticator, head version and commitment in the room's witnessed `did:webvh` log — the one statement a host cannot make, forge, or show two members two versions of. Lifecycle is renewed, never reaped: a room the owner stops renewing goes lapsed → dormant → reclaimable, and a nominated successor can claim a dormant room. + +**A browser can be a member.** `vti-rooms-wasm` compiles the member half (MLS group custody, sealing, opening, the epoch chain) to WebAssembly, so a tab holds its own key and its own group state and asks a host for nothing it could compute itself — the basis for a planned public demo site where someone with no wallet and no agent can be invited into a room by link. The [[vta-browser-plugin|VTA Wallet]] gained a rooms console the same fortnight, driving `rooms/keys/*` at the member's VTA and `rooms/owner/*` at the owner's. + +## Where it stands + +Crates: `vti-rooms` (storage, wire types, authorization — depends only on `vti-common`), `vti-rooms-dtg` (the DTG chain verifier, kept separate so a host can reuse storage without a credential library), `room-host` (binary), `vti-rooms-wasm` (npm-bound, unpublished). The design went through three revisions and two security reviews before the first code merged on 2026-09-03 (#1237); by 2026-09-17 there had been roughly fifty PRs and vti-rooms was at 0.2.9. The `pnm rooms {create,list,get,put,curate,renew}` CLI is the member surface. Still open: the `private` tier's ZK profile, a pull-shaped join for members with no inbox, cryptographic deletion as a verb, and the remaining leg of the epoch chain into an agent's VTA. The crate READMEs say it plainly: *early; the API will change.* diff --git a/content/concepts/decentralized-identifiers.md b/content/concepts/decentralized-identifiers.md index 9a0a5b8..d30f550 100644 --- a/content/concepts/decentralized-identifiers.md +++ b/content/concepts/decentralized-identifiers.md @@ -2,7 +2,7 @@ title: "Decentralized Identifiers (DIDs)" type: concept tags: [did, identity, w3c, standards] -date-updated: 2026-08-19 +date-updated: 2026-09-18 sources: [verifiable-trust-infrastructure, openvtc, didwebvh-rs, affinidi-tdk, affinidi-webvh-service, dtg-credential-spec] --- @@ -43,18 +43,19 @@ Used for simple, self-contained identifiers — typically for credential signing ### did:peer (Private) -Used for [[relationship-credential|Relationship DIDs (R-DIDs)]] — private, pairwise identifiers created for individual relationships. When you connect with someone, you create a unique did:peer for that relationship so your primary Persona DID isn't exposed in every interaction. +Used for per-relationship identifiers — private, pairwise identifiers created for individual [[relationship-credential|relationships]]. When you connect with someone, you create a unique did:peer for that relationship so your persona identifier isn't exposed in every interaction. -## DID Type Taxonomy +## From DID Types to Correlation Scope -The DTG specification defines a formal taxonomy of DID types, each serving a specific role. See [[did-types|DID Types in the DTG]] for the full reference. In brief: +Until September 2026 the DTG specification typed its identifiers — **C-DID** (community), **M-DID** (member), **R-DID** (relationship), **P-DID** (persona); the earlier W-DID was dropped in WD01. **Working Draft 02 retired all four** ([[did-types]] keeps the history). Each name bundled *what the identifier is attached to* — already said by the credentials it appears in — with *how widely it may be correlated*, and the two disagreed as soon as one identifier did two jobs. What remains is the second axis alone, as a holder's declaration: a **[[correlation-scope]]** of `pairwise` (one counterparty), `directed` (a chosen set — where personas live) or `public` (unbounded — the only truthful scope for a community's identifier). Roles are conferred by credentials; scope is declared by the holder; a verifier must never infer scope from the DID method or the identifier's value. -- **C-DID** — Community DID (identifies a VTC or VTN) -- **M-DID** — Member DID (identifies an individual within a community) -- **R-DID** — Relationship DID (unique per relationship, for privacy) -- **P-DID** — Persona DID (for selective persona disclosure) +## What the DTG Needs from a DID Method (WD02) -(The earlier W-DID / Witness DID was dropped in the spec's Working Draft 01; witnesses act under an M-DID or as a VTA.) +The spec mandates no DID method, but WD02 added an informative *DID Method Considerations* section stating what it relies on one for — in terms of the identifier's job, so any method can be judged: + +- **Durable identifiers** — a community's, a member's for the life of a membership, a witnessing VTA's — need **verifiable key history** (which key was authoritative *when a credential was signed*, since DTG credentials are presented long after issuance), **rotation without changing the identifier**, **pre-rotation**, **independence from a single operator or hosting location**, and **discoverable service endpoints**. [[did-webvh|did:webvh]] satisfies the first three; independence must be confirmed separately, because `did:webvh` is relocatable only if `portable` was set in the *first* log entry (methods like `did:scid` avoid the choice). Plain `did:web` provides neither history nor a successor-key commitment, so a verifier cannot tell a legitimate rotation from an attacker's substitution. +- **Narrow-scope identifiers** (`pairwise`, `directed`) need **cheap creation with no registration step**, **no shared resolution origin**, and **no dependency on infrastructure that observes their use** — a party that resolves, or acts as DID-log witness for, many of a person's pairwise identifiers can correlate them regardless of the credentials. `did:peer` and `did:key` fit; anything published at a common web origin does not. +- **Durability and scope are chosen independently.** A `pairwise` identifier toward a community must still stay verifiable for the life of the membership. Expect to **mix methods** — durable issuers on `did:webvh`, subjects on `did:key`/`did:peer`, as the spec's own examples now do — rather than seeking one method for every role. Choosing a method is a privacy decision as much as a key-management one. ## Agent Names — human-readable shortcuts for DIDs @@ -64,11 +65,19 @@ A DID is unreadable to humans. Since July 2026 the ecosystem layers **agent name When an application encounters a DID, it needs to resolve it — look up the DID Document to get the public keys and service endpoints. The [[affinidi-tdk|Affinidi TDK]] provides a high-performance DID resolver that: -1. Supports multiple DID methods (webvh, key, peer, etc.) +1. Supports multiple DID methods (webvh, key, peer, web, webs, scid, jwk, …) 2. Caches resolved documents locally (claiming 250k+ resolutions/sec from cache) 3. Validates the cryptographic integrity of did:webvh history logs 4. Plugs into the [[didwebvh-rs]] library for webvh-specific resolution +### Other methods the resolver understands + +Beyond the three methods above, the TDK resolves `did:web`, `did:jwk`, `did:scid` (`vh:1`, and since August 2026 `ke:1`) and — new in the Dogwood cycle — **`did:webs`**, whose key state is verified through a KERI event log and whose designated aliases surface as `alsoKnownAs`. `did:ethr` and `did:pkh` were re-implemented in-tree (off by default) so that no third-party `ssi-*` crate is compiled anywhere; `did:cheqd` still parses but is no longer resolved. + +### Resolution is public-hosts-only by default (September 2026) + +A DID document is a list of places to connect to, and for web-hosted methods the DID itself names a host to fetch from. In the SEC-4045 hardening sweep of 2026-09-10 → 09-12 the whole stack adopted the same rule: a resolver, a mediator client or a browser DIDComm stack will only fetch a `did:web` / `did:webvh` log from, and only open a connection to, a **public** host. [[didwebvh-rs]] 0.7.0 made `HostPolicy::PublicOnly` the default (special-use names blocked, a guarded DNS resolver, no proxy); the TDK, the VTI, [[openvtc|OpenVTC]] and [[vti-didcomm-js]] each refuse private, loopback and link-local hosts unless explicitly allowed. The reason is server-side request forgery: a DID document that anyone can publish must not be able to steer a VTA, a mediator or a wallet into probing the network it runs on. Local development needs the private-hosts allowance switched on deliberately. + ## The Relationship Between DIDs and Keys In the OpenVTC ecosystem, all keys derive from a single BIP-39 mnemonic seed via [[bip32-key-derivation|BIP-32 derivation]]. This means one backup phrase protects your entire identity. The [[verifiable-trust-agent|VTA]] manages this derivation, creating separate key contexts for different purposes: @@ -78,4 +87,4 @@ In the OpenVTC ecosystem, all keys derive from a single BIP-39 mnemonic seed via - **Relationship keys** — unique keys for each relationship you establish - **Application keys** — for specific services and integrations -See also: [[did-types]], [[did-webvh]], [[verifiable-trust-agent]], [[bip32-key-derivation]] +See also: [[correlation-scope]], [[did-types]], [[did-webvh]], [[verifiable-trust-agent]], [[bip32-key-derivation]] diff --git a/content/concepts/decentralized-trust-graph.md b/content/concepts/decentralized-trust-graph.md index 1e689e5..f82a91a 100644 --- a/content/concepts/decentralized-trust-graph.md +++ b/content/concepts/decentralized-trust-graph.md @@ -2,7 +2,7 @@ title: "The Decentralized Trust Graph (DTG)" type: concept tags: [trust-graph, dtg, trust-over-ip, credentials] -date-updated: 2026-08-19 +date-updated: 2026-09-18 sources: [dtg-credential-spec, dtg-credentials, openvtc, verifiable-trust-infrastructure] --- @@ -15,69 +15,61 @@ The Decentralized Trust Graph is the conceptual model at the heart of the OpenVT In the physical world, you rely on chains of trust. You trust your friend. Your friend trusts their colleague. If your friend vouches for their colleague, you have a reason — not a guarantee, but a reason — to extend some trust. The DTG makes this kind of reasoning cryptographically verifiable in the digital world. The graph is built from two primitives: -- **Nodes** — entities in the world: people, devices, agents, and communities. Each is identified by a [[decentralized-identifiers|Decentralized Identifier (DID)]]. -- **Edges** — created by [[credential-categories|Edge Credentials]]: [[membership-credential|Membership Credentials (VMCs)]] connect entities to communities, and [[relationship-credential|Relationship Credentials (VRCs)]] connect entities to other entities. **Both edge types are bidirectional**: a complete edge requires a pair of credentials, one issued from each side. +- **Nodes** — entities in the world: people, devices, AI agents, communities, networks, and (since Working Draft 02 of the spec) **services** — an operated endpoint with its own identifier, such as a message mediator, a DID host or a trust registry, as distinct from the provider that runs it. The list is illustrative, not closed: what makes something a node is that it holds a [[decentralized-identifiers|verifiable identifier]] and forms edges. Each identifier carries a holder-declared [[correlation-scope]]. +- **Edges** — created by [[credential-categories|Edge Credentials]]: [[membership-credential|Membership Credentials (VMCs)]] connect entities to communities, [[relationship-credential|Relationship Credentials (VRCs)]] connect entities to other entities, and [[delegation-credential|Delegation Credentials (VDCs)]] connect a principal to a party appointed to act in its name. **Every edge is bidirectional**: a complete edge requires a pair of credentials, one from each side — and the second half is the counterparty's *consent*. -Additional [[credential-categories|Annotation Credentials]] ([[endorsement-credential|endorsements]], [[witness-credential|witnesses]], [[persona-credential|personas]]) attach data to existing edges without creating new graph structure. +[[credential-categories|Annotation Credentials]] ([[endorsement-credential|endorsements]], [[witness-credential|witnesses]], [[persona-credential|personas]]) attach data to existing structure. Two credentials stand apart from the graph structure: the [[invitation-credential|VIC]] bootstraps a node into a community, and the [[authority-credential|VAC]] confers permission to act within a scope a node governs. Anyone can traverse the graph to discover trust paths between two entities. The credentials are cryptographically signed, so every edge is verifiable. The graph is decentralized — no single authority controls it, and no single point of failure can break it. ## How Trust Is Built -Trust in the DTG is built incrementally, from the ground up. The graph supports two complementary patterns: peer-to-peer relationships between individuals, and community-anchored relationships that gain extra proof guarantees from a shared VTC. - ### Form Relationships Directly -The base case is two people who know and trust each other exchanging [[relationship-credential|Relationship Credentials (VRCs)]]. Each party issues one VRC to the other; together the two VRCs form a complete edge in the graph. Neither party needs to be a member of any community for this to be meaningful — the relationship stands on the cryptographic attestations themselves and on whatever real-world context the parties bring to it. - -The protocol: - -1. One person sends a relationship request via [[didcomm|DIDComm]] -2. The other accepts -3. Both finalize the relationship, creating a private channel with a unique [[did-types|R-DID]] -4. Each party issues a VRC to the other +The base case is two people who know and trust each other exchanging [[relationship-credential|VRCs]]. Each issues one to the other; together the two form a complete edge. Neither needs to belong to any community — the relationship stands on the attestations themselves and whatever real-world context the parties bring. -The spec requires each entity to generate a **new, unique R-DID for every relationship**, ensuring privacy. +The protocol: one person sends a relationship request via [[didcomm|DIDComm]] or [[trust-spanning-protocol|TSP]]; the other accepts; both finalize, creating a private channel under per-relationship identifiers; each issues a VRC. The spec RECOMMENDS a **`pairwise`** identifier per relationship — declared as such, never reused with a second counterparty. ### Join a Community for Anchored Proofs and Personhood -Membership in a [[verifiable-trust-community|Verifiable Trust Community]] is optional but powerful. A [[membership-credential|Membership Credential (VMC)]] connects a participant to a community whose governance defines who counts as a member, and a bidirectional pair of VMCs (one from the community to the member, one back) forms a complete membership edge. Communities can themselves be members of [[verifiable-trust-network|Verifiable Trust Networks (VTNs)]] via the same bidirectional pattern, creating a VTN → VTC → member hierarchy. +Membership in a [[verifiable-trust-community|Verifiable Trust Community]] is optional but powerful. A community issues a VMC *grant*, and the member issues an *acknowledgement* back carrying a digest of the grant — the member's consent artifact. Without it a community cannot prove anyone's membership to a third party. Communities can themselves be members of [[verifiable-trust-network|Verifiable Trust Networks (VTNs)]] via the same pair. -Joining a VTC unlocks two things that pure peer-to-peer VRCs cannot provide on their own: +Joining unlocks two things pure peer-to-peer VRCs cannot provide: -- **Personhood attestation.** When the community's governance enforces real human personhood and one-membership-per-person rules, the VMC qualifies as a [[personhood-credential|Personhood Credential (PHC)]] — determined by the community's [[trust-registries|trust registry]]. -- **Community-anchored relationship proofs.** A holder can construct a ZKP showing that both parties to a VRC hold VMCs from the same community, lending the relationship the community's governance assurances without exposing identifying details. See [[relationship-credential]]. +- **Personhood attestation.** When the community's governance enforces real human personhood and one-membership-per-person, the grant is a [[personhood-credential|Personhood Credential (PHC)]] — determined by the community's [[trust-registries|trust registry]]. +- **Community-anchored relationship proofs.** A holder can construct a [[zero-knowledge-proofs|ZKP]] showing both parties to a VRC hold VMCs from the same community, lending the relationship the community's assurances without exposing identifiers. -VRCs between people who *don't* share a community are still valid trust attestations; they just can't be proven through community-anchored ZKPs. +### Appoint and Empower Agents + +WD02 gives the graph a vocabulary for the AI-agent case. A person who wants an agent to act **as itself**, with strictly less than the person holds, attenuates an [[authority-credential|Authority Credential]] to it — four hours of read-only access to one room, verified by walking the chain back to the room. A person who wants an agent's acts **attributed to them** issues a [[delegation-credential|Delegation Credential]], which the agent accepts; the verifier then asks whether *the person* may do the act. Which of the two a community admits for agents is governance; that a verifier can always tell which it was shown is what keeping them separate buys. ### Annotate with Endorsements and Witnesses -Trust relationships — whether peer-to-peer or community-anchored — can be strengthened through [[credential-categories|Annotation Credentials]]: - **[[endorsement-credential|Endorsements (VEC)]]** — "I endorse this person's skills in X" -- **[[witness-credential|Witnesses (VWC)]]** — "I witnessed that this relationship is genuine" (especially powerful via the [[witnessed-vrc-exchange|Witnessed VRC Exchange Protocol]]) -- **[[persona-credential|Personas (VPC)]]** — selectively linking a persona identity to a relationship +- **[[witness-credential|Witnesses (VWC)]]** — "I witnessed this edge being formed" (especially powerful via the [[witnessed-vrc-exchange|Witnessed VRC Exchange Protocol]]) +- **[[persona-credential|Personas (VPC)]]** — selectively linking a persona to a relationship + +On the spec's `main` branch endorsements and witness attestations become profiles of one [[statement-credential|Statement Credential]] — a signed statement by one node about another, under a governed predicate. ### Scale Through Networks -Communities can federate into [[verifiable-trust-network|Verifiable Trust Networks (VTNs)]], enabling community-anchored trust paths to traverse community boundaries. VTNs and their member VTCs exchange bidirectional VMC pairs, creating a shared trust anchor across independent communities. +Communities federate into [[verifiable-trust-network|VTNs]]. Since WD02 a VTN is best understood as one example of the **anchor set** a verifier accepts: an edge credential is "verifiable as a DTG edge *by a given verifier*" when its proof verifies and the issuer's membership in a VTC in that verifier's anchor set is complete — by disclosing the VMC pair or by community-anchored proof. The same edge may count for one verifier and not another, and neither is wrong. ## Traversing the Graph -The power of the DTG is in traversal. Imagine you're evaluating a contributor to an open-source project: +Imagine you're evaluating a contributor to an open-source project: -1. They present their [[did-types|M-DID]] +1. They present an identifier they have chosen to make `directed` or `public` for this purpose 2. You look up their credentials in the graph 3. You find they have VRCs with three people you already trust 4. Those people have endorsement credentials attesting to their coding skills 5. Two of those endorsements have witness credentials from an in-person meetup -You've now established a multi-path, multi-evidence trust assessment — all without a central authority, all cryptographically verifiable. - -When the parties share a community, the holder can additionally construct a community-anchored ZKP: a proof that they hold the VRC, that they hold a VMC from a community, and that the counterparty holds a VMC from the *same* community — without revealing the underlying DIDs. This anchors that proof within the community's governance context. It's one proof construction, not the only way a VRC can be presented or verified. +You've now established a multi-path, multi-evidence trust assessment — all without a central authority, all cryptographically verifiable. Where the parties share a community, the holder can additionally construct a community-anchored ZKP. The [[verifiable-trust-infrastructure|VTI]] draws this graph, distinguishing half-edges from complete edges (#1073, #1213). ## The DTG Specification -The credential types that populate the DTG are defined by the **Trust Over IP Foundation's DTG Working Group Credentials Task Force** in the [[dtg-credential-spec|DTG Core Credentials specification]] — since July 2026 a formal ToIP deliverable at v1.0 Working Draft 01, which also gives *node* and *edge* their glossary definitions (a **DTG node** is an entity — person, device, AI agent, community — identified by at least one DTG verifiable identifier and reachable via a [[vta-topology|VTA]]; a **DTG edge** is a cryptographically verifiable trust relationship between two nodes, formed by a pair of edge credentials). The [[dtg-credentials|dtg-credentials]] library provides the Rust implementation. See [[dtg-credentials-overview]] for the complete taxonomy and [[credential-categories]] for the functional classification. +The credential types that populate the DTG are defined by the **Trust Over IP Foundation's DTG Working Group Credentials Task Force** in the [[dtg-credential-spec|DTG Core Credentials specification]] — a formal ToIP deliverable at **v1.0 Working Draft 02** (tagged 2026-09-07), with `main` heading for Working Draft 0.4.0. It gives *node* and *edge* their glossary definitions: a **DTG node** is an entity identified by at least one DTG verifiable identifier and reachable via a [[vta-topology|VTA]]; a **DTG edge** is a cryptographically verifiable trust relationship between two nodes, formed by a pair of edge credentials. One boundary the spec draws explicitly: a VMC binds a member to a node that *has members* — a person is not a collective, and "membership in a person" is a VRC or a VDC, never a VMC. The [[dtg-credentials|dtg-credentials]] library provides the Rust implementation. See [[dtg-credentials-overview]] for the complete taxonomy and [[credential-categories]] for the functional classification. ## Why Decentralized? @@ -86,11 +78,12 @@ The "decentralized" in DTG is load-bearing. In a centralized trust model (like a In the DTG: - Trust is peer-to-peer — no root authority - Multiple independent paths can corroborate trust -- No single entity can revoke your participation +- No single entity can revoke your participation — and no community can claim your membership without your signed consent - The graph is resilient to individual node compromise - Communities set their own trust policies via [[trust-registries|trust registries]] - Networks federate communities under shared governance +- Authority chains and delegation chains are verified from what the holder presents, never fetched — so verification stays offline and nobody learns when a credential is used This is the [[first-person-network|First Person Network]] vision: identity and trust that belong to the individual, not to any institution. -See also: [[dtg-credentials-overview]], [[credential-categories]], [[did-types]], [[trust-registries]], [[verifiable-trust-community]], [[verifiable-trust-network]] +See also: [[dtg-credentials-overview]], [[credential-categories]], [[correlation-scope]], [[trust-registries]], [[verifiable-trust-community]], [[verifiable-trust-network]], [[authority-credential]], [[delegation-credential]] diff --git a/content/concepts/delegation-credential.md b/content/concepts/delegation-credential.md new file mode 100644 index 0000000..ce50d21 --- /dev/null +++ b/content/concepts/delegation-credential.md @@ -0,0 +1,61 @@ +--- +title: "Delegation Credential (VDC)" +type: concept +tags: [credentials, dtg, delegation, agents, edge, spec] +date-updated: 2026-09-18 +sources: [dtg-credential-spec, dtg-credentials] +--- + +# Delegation Credential (VDC) + +The Verifiable Delegation Credential attests that one entity (the **delegator**) has appointed another (the **delegate**) to act **in the delegator's name**, for a bounded set of acts, for a limited period, revocably. It is the third [[credential-categories|Edge Credential]], added in Working Draft 02 of the [[dtg-credential-spec|spec]] (PR #19, 2026-09-06), and the credential that lets the graph say "this agent may act for this person — within these bounds." + +## Why it exists + +The spec's glossary had assumed delegation for months — a persona is controlled by "the person it identifies (or their delegate)" — and AI agents are first-class DTG nodes. But every WD01 credential *attests* that something is true; none *establishes* that one party may stand in for another. Establishing representation needs verification steps that evaluating a claim does not: scope containment, chain resolution, invocation binding, timely revocation. A distinct type string forces a verifier onto that path. + +## Delegation is not authority + +The line the spec draws hardest, and the one that decides which credential to reach for: + +| Question | Credential | The act is attributed to | +|---|---|---| +| May this party do this thing, *as itself*? | [[authority-credential|VAC]] | the party itself | +| May this party act *in another's name*? | **VDC** | the entity it stands in for | + +Neither implies the other. A service with access to a mailbox may read it as itself; it has not been appointed to send mail in the owner's name. A delegate appointed to correspond in someone's name holds that appointment whether or not it has a mailbox — and without one, gets nowhere. Guardianship, succession and similar mandates are *not* expressed by a VDC. + +**A VDC moves the permission question; it does not answer it.** When a delegate presents a VDC, the verifier does not ask what the *delegate* may do. It substitutes the delegator and asks what it would have asked of the delegator directly. Three independent checks: (1) may this party act in that name — the VDC; (2) may the *delegator* do this — membership, governance, an IDVC, a VAC, the verifier's own policy; (3) must the delegate independently qualify — a governance call. Reach is the **intersection** of (1) and (2), never the union. Nothing the delegator holds is copied to the delegate; withdrawing the delegator's own permission stops every delegate at once without revoking a single VDC. + +## What it contains + +- `type` includes `DelegationCredential`; `issuer` is the delegator (a person, device, agent, a VTC delegating to a service, or a persona identifier); `credentialSubject.id` is the delegate +- `delegation.scope` — the acts, as opaque strings from the governing VTC/VTN vocabulary, compared by exact equality; REQUIRED and non-empty on a grant +- `delegation.parent` — digest of the VDC this one derives from; absent means a **root delegation** +- `delegation.maxDepth` — further re-delegations permitted; absent or `0` prohibits it. **Single-hop is the default**; a value above `0` is the delegator's only way to authorise re-delegation +- `delegation.accepts` — on the acceptance only: digest of the grant being accepted +- `validUntil` REQUIRED; `credentialStatus` CONDITIONAL + +## Grant plus acceptance = one edge + +Like a [[membership-credential|VMC]] pair, a delegation is two credentials. The **grant** (no `accepts`) is the delegator stating the appointment; the **acceptance** (with `accepts`, issued by the delegate to the delegator, carrying no scope of its own) is the delegate taking on the appointment and the accountability of acting in another's name. **The acceptance is REQUIRED**: a grant alone establishes what the delegator appointed, not what the delegate agreed to, and a delegator cannot forge the countersignature — nor can a party holding only the delegate's key manufacture appointments. The edge is directional, and a VDC neither requires nor implies a [[relationship-credential|VRC]] between the parties. + +The grant is a credential; the **invocation** — "the delegate is acting in the delegator's name, now, to do this" — is meaningful only inside its exchange and is a Trust Task artifact, per the spec's [[trust-task-context-binding|credential-or-artifact test]]. + +## Chains, invocation, revocation + +Where a VDC carries `parent`, the verifier evaluates the whole chain: every link valid on its own; each `scope` a subset of its parent's; no `validUntil` later than the parent's; depth bounded by every ancestor's `maxDepth`; a root issued by the principal the verifier intends to deal with. Presenting a derived VDC discloses the whole ancestry, principal included — one more reason single-hop is the default and chain validity is a candidate [[zero-knowledge-proofs|ZK predicate]]. + +**Invocation Binding: a VDC is not a bearer token.** A verifier MUST NOT accept a party as acting in the delegator's name unless it demonstrates control of `credentialSubject.id` at the time of the request; how that is carried belongs to the Trust Task Protocols spec. + +**Revocation prefers expiry.** A verifier must establish that an appointment is in force *without contacting the delegator*: either a `validUntil` short enough that expiry bounds exposure, with withdrawal by declining to re-issue, or a `credentialStatus` it can check. Status is REQUIRED only where validity exceeds the governing freshness window, because a status lookup is a live correlation surface — whoever hosts the list learns which verifier checked which credential, and when. + +## Personhood laundering + +A [[personhood-credential|PHC]] says its holder is one real person. Because a delegate's acts are attributed to the delegator, a verifier that cannot tell the two apart may credit an agent with its principal's personhood — or count several agents of one person as several people. Verifiers must treat an act under a VDC as *the delegate acting in the delegator's name*, never as the delegator in person, and communities whose governance rests on personhood should say whether delegated acts count at all. + +## Implementation status + +[[dtg-credentials]] 0.6.0 shipped `new_vdc` as little more than a type string. 0.7.0 (WD02) made it real: `DelegationGrant`, `new_delegate_vdc` (the acceptance, from the grant's wire form), `accepts()`, opt-in `redelegate`, and `delegation::verify_chain`, which returns the principal and appointed acts — deliberately not whether the act is permitted. 0.9.1 added Invocation Binding (the leaf must appoint the *presenter*); 0.10.0 (on `main`) replaced `new_delegate_vdc` with `new_delegate_vdc_for`, which refuses a grant that does not name the party answering it. `credentialStatus` is settable but never resolved. In the [[data-rooms|data-room]] example a member appoints a service by VDC alongside an agent equipped by attenuated VAC — "the same member, two credentials, and a verifier that can always tell which it was shown." + +See also: [[authority-credential]], [[credential-categories]], [[dtg-credentials-overview]], [[personhood-credential]], [[trust-task-context-binding]] diff --git a/content/concepts/did-types.md b/content/concepts/did-types.md index 72ba954..653da3b 100644 --- a/content/concepts/did-types.md +++ b/content/concepts/did-types.md @@ -1,54 +1,55 @@ --- -title: "DID Types in the DTG" +title: "DID Types in the DTG (retired)" type: concept -tags: [did, taxonomy, identity, dtg] -date-updated: 2026-08-19 -sources: [dtg-credential-spec, openvtc] +tags: [did, taxonomy, identity, dtg, history] +date-updated: 2026-09-18 +sources: [dtg-credential-spec, dtg-credentials, openvtc] --- -# DID Types in the DTG +# DID Types in the DTG (retired) -The [[decentralized-trust-graph|Decentralized Trust Graph]] uses a taxonomy of [[decentralized-identifiers|DID]] types, each serving a specific role. Understanding these types is key to understanding how identity and privacy work in the ecosystem. +> **The four-way DID taxonomy — C-DID, M-DID, R-DID, P-DID — was retired in Working Draft 02 of the [[dtg-credential-spec|DTG Core Credentials spec]] (PR #30, 2026-09-05).** Its replacement is [[correlation-scope]]: a holder-declared `pairwise` | `directed` | `public` width, with the identifier's *role* read from the credentials it appears in. This page is kept so that older wiki pages, code comments and discussions that still say "M-DID" or "R-DID" can be understood; new writing should use the scope vocabulary. -Since Working Draft 01 of the [[dtg-credential-spec|spec]] (July 2026) the official list is **four**: the earlier **W-DID** (Witness DID) was dropped — a witness acts under an M-DID or as a VTA applying community policy. The spec frames these as *DTG verifiable identifiers (VIDs)*: this version uses DIDs exclusively, but future versions may admit other ToIP VIDs such as X.509 or KERI AIDs. Every DTG node is identified by at least one DTG VID and reached through a [[vta-topology|VTA]]. +## Why it was retired -## The Taxonomy +Each type named two independent things in one token: *what the identifier is attached to* (a relationship, a membership, a community, a persona) and *how widely it may be correlated*. The two disagreed whenever an identifier did two jobs — and WD01 explicitly permitted an M-DID inside a VRC, at which point the same identifier was "an M-DID" and "an R-DID" at once. The first fact was redundant (a VMC already says its subject is a member; a VRC already says its issuer is a party to a relationship); only the second needed a home. WD02 kept that one axis as a declaration by the holder, deleted the four glossary entries outright, and lists the old names under *Avoid* in its contributor guide. See [[correlation-scope]] for the model that replaced them. -| DID Type | Full Name | Who/What It Identifies | Typical DID Method | -|----------|-----------|----------------------|-------------------| -| **C-DID** | Community DID | A [[verifiable-trust-community\|VTC]] or [[verifiable-trust-network\|VTN]] | [[did-webvh\|did:webvh]] | -| **M-DID** | Member DID | An individual member within a community | [[did-webvh\|did:webvh]] | -| **R-DID** | Relationship DID | A specific relationship between two individuals | did:peer | -| **P-DID** | Persona DID | A persona identity linked to a relationship | [[did-webvh\|did:webvh]] | +## What each old name meant, and what it maps to now -## Privacy Through DID Separation +| Old type | Meant | Typical method | In WD02 terms | +|---|---|---|---| +| **C-DID** — Community DID | The identifier of a [[verifiable-trust-community\|VTC]] or [[verifiable-trust-network\|VTN]] | [[did-webvh\|did:webvh]] | "the VTC's own identifier", which can only truthfully be declared **`public`** — a community that cannot be found cannot be joined | +| **M-DID** — Member DID | The identifier a member used with a community (and, if they chose, across communities and in VRCs) | did:webvh / did:key | an identifier that *has a VMC*; **`pairwise`** if used only with the community, **`directed`** if also used with fellow members or across communities | +| **R-DID** — Relationship DID | A per-relationship identifier, never reused | did:peer | an identifier declared **`pairwise`**; reuse with a second counterparty now *falsifies the declaration* rather than merely breaking a rule | +| **P-DID** — Persona DID | A persona identity asserted via a VPC | did:webvh | an identifier under which a [[persona-credential\|persona]] is asserted, ordinarily declared **`directed`** ("a private persona") or `public` ("a public persona") | +| **W-DID** — Witness DID | *(dropped earlier, in WD01)* | — | a witness issues from its own identifier — a member's, or a VTA's under VTC policy — which is `directed` at minimum | -The separation of DID types is a deliberate privacy design. The spec requires that **each entity MUST generate a new, unique R-DID for every single entity they connect with**, even within the same community. This means: +The privacy design the old separation encoded survives intact, just stated differently: the spec still RECOMMENDS a `pairwise` identifier per relationship, still expects a persona to be a deliberate, selective act of correlation, and still treats a community's identifier as the one thing that must be findable. What changed is that these are now the holder's *declarations*, and a verifier MUST NOT infer scope from an identifier's value, DID method, or where it was encountered. -- Your M-DID identifies you within a community but isn't exposed in individual relationships -- Each R-DID is unique to one relationship, so counterparties can't correlate your connections -- P-DIDs let you selectively reveal personal information to specific relationships -- Witnesses attest under an existing identity — an M-DID, or a VTA acting under community policy — rather than a dedicated type +## How credentials name their parties now (WD02) -## How They Map to Credentials +| Credential | Issuer | Subject | +|---|---|---| +| [[membership-credential\|VMC]] (grant) | the VTC/VTN's own identifier (`public`) | the member's chosen identifier, at whatever scope the member declared | +| VMC (acknowledgement) | the member, from the same identifier the grant named | the VTC/VTN | +| [[relationship-credential\|VRC]] | source party (`pairwise` RECOMMENDED; `directed` permitted) | target party as used in this relationship | +| [[delegation-credential\|VDC]] | delegator (ideally a `directed` identifier scoped to the context) | delegate | +| [[invitation-credential\|VIC]] | the VTC/VTN or an authorised member | the identifier the prospect proposes to use | +| [[persona-credential\|VPC]] | the persona's identifier (`directed`) | the counterparty as used in the relationship | +| [[endorsement-credential\|VEC]] | endorser | endorsed party | +| [[witness-credential\|VWC]] | witness (`directed` at minimum) | the *issuer* of the witnessed edge credential | +| [[authority-credential\|VAC]] | the party governing the scope, or an attenuating holder | the party receiving authority | -| Credential | Issuer DID | Subject DID | -|-----------|-----------|------------| -| [[membership-credential\|VMC]] | C-DID (community) | M-DID (member) or C-DID (for VTN→VTC) | -| [[relationship-credential\|VRC]] | R-DID or M-DID | R-DID or M-DID (counterparty) | -| [[invitation-credential\|VIC]] | C-DID or authorized M-DID | Invitee's DID | -| [[persona-credential\|VPC]] | P-DID | Counterparty's DID | -| [[endorsement-credential\|VEC]] | Endorser's DID | Endorsee's DID | -| [[witness-credential\|VWC]] | M-DID, or the DID of a VTA acting under VTC policy | Issuer of the attested VRC (the observed party) | +## In the OpenVTC implementation -## BIP-32 Derivation +[[openvtc|OpenVTC]] and the [[verifiable-trust-agent|VTA]] were built against the old vocabulary and their key layout still reflects it — which is fine, because the derivation paths express *purpose*, not scope: -In the [[openvtc|OpenVTC]] implementation, all these DIDs derive from a single seed via [[bip32-key-derivation|BIP-32 derivation]]: +| Path | Old name | Now | +|---|---|---| +| `m/1'/0'/` | Persona keys (P-DID) | the persona identifier a member joins communities with | +| `m/2'/1'/` | WebVH management keys | did:webvh update keys | +| `m/3'/1'/1'/N` | Relationship keys (one R-DID per relationship) | one `pairwise`-scope identifier per relationship (the default since Dogwood, openvtc #254/#255 on VTI #1061) | -| Path | DID Type | -|------|----------| -| `m/1'/0'/` | Persona keys (P-DID) | -| `m/2'/1'/` | WebVH management keys | -| `m/3'/1'/1'/N` | Relationship keys (R-DIDs, one per relationship) | +See [[bip32-key-derivation]]. The [[dtg-credentials]] crate dropped the retired names from its documentation in 0.7.0 and implements nothing further for scope, because the spec has not yet named the property that carries a declaration. -See also: [[decentralized-identifiers]], [[decentralized-trust-graph]], [[bip32-key-derivation]] +See also: [[correlation-scope]], [[decentralized-identifiers]], [[decentralized-trust-graph]], [[bip32-key-derivation]] diff --git a/content/concepts/did-webvh.md b/content/concepts/did-webvh.md index 4fdcba0..1e31605 100644 --- a/content/concepts/did-webvh.md +++ b/content/concepts/did-webvh.md @@ -2,8 +2,8 @@ title: "did:webvh — Web DIDs with Verifiable History" type: concept tags: [did, did-webvh, identity, dif] -date-updated: 2026-04-09 -sources: [didwebvh-rs, affinidi-webvh-service, openvtc] +date-updated: 2026-09-18 +sources: [didwebvh-rs, affinidi-webvh-service, openvtc, dtg-credential-spec] --- # did:webvh — Web DIDs with Verifiable History @@ -72,6 +72,10 @@ When someone resolves the DID: 2. It validates every log entry in the chain — checking signatures, hash links, timestamps, and witness proofs 3. It returns the current DID document, with confidence in the complete history +## What the DTG Spec Says About It (WD02) + +Working Draft 02 of the [[dtg-credential-spec|DTG Core Credentials spec]] switched its examples from `did:web` to `did:webvh` for **durable** identifiers — a community's, a witnessing VTA's — because plain `did:web` has no verifiable history and no successor-key commitment, so a verifier cannot tell a legitimate rotation from an attacker's substitution, nor establish which key was authoritative when a credential was signed. Its informative *DID Method Considerations* adds two cautions worth knowing. First, **portability is decided at inception**: a `did:webvh` log can be relocated only if `portable` was set in the *first* log entry; it defaults to off and a later entry cannot enable it, so an identifier expected to outlive its hosting arrangement must be created with it on. Second, **the resolution layer is a correlation channel**: several of a person's narrow-scope ([[correlation-scope|`pairwise` / `directed`]]) identifiers resolving through one web origin, one registry or one set of DID-log witnesses can be linked — and their use observed — by that infrastructure regardless of what the credentials disclose, which is why the spec steers such identifiers toward `did:peer` / `did:key` and expects deployments to mix methods. (The spec also disambiguates *DID log witness* — countersigning DID document versions — from the *witness* of a [[witness-credential|VWC]], which attests to an edge.) See [[decentralized-identifiers]]. + ## Implementation - [[didwebvh-rs]] — the Rust library implementing the did:webvh specification (creation, resolution, update, validation, witness management) diff --git a/content/concepts/didcomm.md b/content/concepts/didcomm.md index 721e969..16aaab7 100644 --- a/content/concepts/didcomm.md +++ b/content/concepts/didcomm.md @@ -1,16 +1,16 @@ --- title: "DIDComm — DID-based Secure Messaging" type: concept -tags: [didcomm, messaging, encryption, communication] -date-updated: 2026-08-19 -sources: [affinidi-tdk, verifiable-trust-infrastructure, openvtc] +tags: [didcomm, messaging, encryption, communication, mediators, federation, security] +date-updated: 2026-09-18 +sources: [affinidi-tdk, verifiable-trust-infrastructure, openvtc, vti-didcomm-js, vta-browser-plugin] --- # DIDComm — DID-based Secure Messaging ## What It Is -DIDComm (DID Communication) is a protocol for secure, private messaging between parties identified by [[decentralized-identifiers|DIDs]]. It's the communication layer of the OpenVTC ecosystem — how participants exchange relationship requests, credentials, and protocol messages. +DIDComm (DID Communication) is a protocol for secure, private messaging between parties identified by [[decentralized-identifiers|DIDs]]. It is one of the two communication carriers of the OpenVTC ecosystem — how participants exchange relationship requests, credentials, and protocol messages — alongside the [[trust-spanning-protocol|Trust Spanning Protocol]], which the stack now prefers when both ends can speak it. The ecosystem uses **DIDComm v2**, which provides: @@ -29,21 +29,44 @@ DIDComm also decouples identity from infrastructure. With HTTPS, you need to kno When two OpenVTC users interact: -1. The sender looks up the recipient's DID Document to find their messaging endpoint (usually a mediator) -2. The sender encrypts the message using the recipient's public key (authcrypt — authenticated encryption) -3. The encrypted message is wrapped in a forward envelope for the mediator -4. The mediator holds the message until the recipient polls for it -5. The recipient decrypts the message using their private key +1. The sender looks up the recipient's DID Document to find their messaging endpoint (usually a mediator's DID — the ecosystem publishes the mediator's DID, not its URL, so a user's document is not coupled to a hostname) and vets that endpoint against its egress policy before dialling it +2. The sender encrypts the message using the recipient's public key (authcrypt — authenticated encryption; anoncrypt-only envelopes are refused by default) +3. The encrypted message is wrapped in a `routing/2.0` forward envelope addressed to the mediator by DID +4. The mediator live-streams it if the recipient is connected at that instant, otherwise stores it until the recipient collects it (message-pickup 3.0) — and, if the recipient is homed on another mediator, relays it there +5. The recipient decrypts the message using their private key and acknowledges it only after durably handing it off -The [[openvtc|OpenVTC service]] runs a background daemon that polls a mediator for incoming messages, processing protocol requests like relationship proposals and VRC exchanges. +The [[openvtc|OpenVTC]] TUI keeps one listener per persona on the TDK's reliable delivery layer (the separate `openvtc-service` daemon was retired in mid-2026), processing protocol requests like relationship proposals and VRC exchanges as they arrive. ## Mediators -A mediator is a relay service that holds encrypted messages for recipients who aren't always online. The [[affinidi-tdk|Affinidi TDK]] provides a mediator implementation with production features like circuit breakers, rate limiting, and graceful shutdown. +A mediator is a relay service that holds encrypted messages for recipients who aren't always online. The [[affinidi-tdk|Affinidi TDK]] provides a mediator implementation with production features like circuit breakers, per-IP and per-DID rate limiting, graceful shutdown and an `explicit_allow` ACL mode that gates authentication itself. -Mediators see message metadata (who is sending to whom, when, message size) but cannot read message content. For stronger privacy, future versions may support onion routing or mix networks. +Mediators see message metadata (who is sending to whom, when, message size) but cannot read message content. For stronger metadata privacy the ecosystem's answer is not a DIDComm feature but TSP's nested routing (see below). -Two things changed about how the ecosystem *uses* mediators in mid-2026. First, **reliability became explicit**: the TDK's `affinidi-messaging-delivery` layer gives senders a durable outbox with delivery evidence and receivers an ack-only-after-durable-handoff rule, fixing a class of silently lost messages (a send that returned `Ok` with no socket; a message deleted at the mediator before the handler ran). A mediator live-streams only to a recipient connected *at that instant* and otherwise stores the message — so clients now also **collect stored mail on every connect** (message-pickup 3.0) rather than waiting to be pushed; OpenVTC v0.3.0 is the visible result. Second, **authentication tightened**: the TDK's SDK now rejects unauthenticated (anoncrypt-only) envelopes by default and the mediator's `explicit_allow` ACL mode gates authentication itself. The mediator also learned **DIDComm v1** (forward, coordinate-mediation, pickup) in August 2026 for interop with Aries/Credo-lineage wallets. +### Reliability and pickup (mid-2026) + +Two things changed about how the ecosystem *uses* mediators in mid-2026. First, **reliability became explicit**: the TDK's `affinidi-messaging-delivery` layer gives senders a durable outbox with delivery evidence and receivers an ack-only-after-durable-handoff rule, fixing a class of silently lost messages (a send that returned `Ok` with no socket; a message deleted at the mediator before the handler ran). A mediator live-streams only to a recipient connected *at that instant* and otherwise stores the message — so clients now also **collect stored mail on every connect** (message-pickup 3.0) rather than waiting to be pushed; OpenVTC v0.3.0 is the visible result. Second, **authentication tightened**: the TDK's SDK rejects unauthenticated (anoncrypt-only) envelopes by default and enforces `from == skid`. + +### The addressing contract: DID-addressed, no keylist + +A question from a downstream transport binding prompted the mediator to write down what it had only ever implied (`docs/mediation-and-routing.md`, September 2026): **DIDComm v2 on this mediator is DID-addressed and has no keylist.** A `routing/2.0` forward names its next hop as a DID; the mediator hashes that DID, finds the account, and delivers — there is no verkey, no routing key and nothing a keylist could populate. A v2 client registers nothing before it can receive; the mediator creates the account itself on first forward or on authentication. This is why `coordinate-mediation` is deliberately *not* in the advertised protocol set, and a test (`coordinate_mediation_is_not_advertised`) makes adding it a visible decision. The keylist exists only for **DIDComm v1** (Aries RFC 0019), which the mediator learned in August 2026 for interop with Aries/Credo-lineage wallets — a v1 envelope carries no DID, so `coordinate-mediation/1.0` keylist-update is how a wallet manufactures a stable identifier the mediator can route to. What *does* gate reachability is the ACL: `RECEIVE_MESSAGES`, `RECEIVE_FORWARDED`, and `local_direct_delivery_allowed`. + +### One refusal to rule them out: `delivery.refused` + +Since mediator 0.25.0 (the TSP Rev 3 release) **every refused delivery answers the same way** — `delivery.refused`, "Message not accepted for delivery", HTTP 403 — over DIDComm and TSP alike. Five earlier problem codes were retired because each told an unauthenticated sender something it had no business learning: whether a DID has an account here and, if so, which ACL rule turned it away. Distinguishing them was a probing oracle. The real reason is still logged against the session for operators. + +### Multi-mediator federation + +"Multi-mediator" means two different things and only one is federation: a *cluster* is several mediator processes sharing one Redis, one DID and one account store; a *federation* is several **independent** mediators, each with its own DID, accounts and operator, relaying to each other so a user homed on mediator A can reach a user homed on mediator B. Federation had worked on paper since June 2026 and failed in production at Dogwood — with the default `RelayMode::Blind` a mediator refused *every* relayed message as a session mismatch, and the problem report it sent when abandoning a forward went out as bare plaintext that every authcrypt-only client discards — because it had never been written down. `docs/multi-mediator.md` (September 2026) now documents the two hop shapes (a *double forward* arrives at B as a forward and needs `RECEIVE_FORWARDED`; `send_to`'s *single forward* arrives as direct delivery and needs `local_direct_delivery_allowed`), how the sending mediator classifies a next hop as local, remote or indirect-by-DID (only one hop of indirection is followed; publish the mediator's DID, not its URL, in a user's service entry), **blind vs rewrap relay** (rewrap hides the original sender's key id from the wire and lets B identify and allow-list the relaying peer, but both sides must run it), the four accounts one cross-mediator delivery consults — including the non-obvious one, that the *peer mediator's* DID needs an account with `RECEIVE_FORWARDED` — and a symptom-to-cause table. "The shipped default grants neither forwarded bit, so it is not a federation configuration." + +Two gaps that document exposed were then closed: an **inter-mediator relay over WebSocket** (0.22.2) that admits an anonymous relay hop only when the operator opted in and only for a socket offering the `relay-ack` subprotocol, answering each frame with a **`RelayAck`** carrying the mediator error code on refusal — a problem report cannot be packed to a relay session, which has no DID — with a bounded one-hour lifetime and raw-TSP mode forced off; and management Trust Tasks answered over TSP as well as DIDComm (0.22.3), so a TSP-only client can administer its own ACL. + +### Egress policy: `affinidi-net-guard` and `net-guard` + +Much of what a messaging client dials is named by data someone else controls: a mediator's REST, auth and WebSocket URLs come out of *its* DID document, a VTA's base URL is copied from one at onboarding, a `did:web` value names the host its document is fetched from, and an HTTP server names its own redirect target. Left unchecked, each is a server-side request forgery aimed at loopback, a private network or a cloud-metadata endpoint. The cross-repo **SEC-4045** review (September 2026) closed this in both language stacks: + +- **`affinidi-net-guard`** (TDK, ADR 0006) — a leaf crate with no `affinidi-*` dependency, so [[didwebvh-rs]] and external clients can use it. `EgressPolicy::vet` checks scheme, userinfo, port, allow-list, special-use names (`localhost`, `*.local`, `*.internal`, `*.home.arpa`, single-label names) and literal addresses; a `GuardedResolver` refuses any DNS answer in non-routable space and pins the connection to the vetted addresses (closing DNS rebinding); and a `GuardedClient` disables proxies, follows no redirects and caps bodies — because `reqwest` never consults a custom resolver for an IP-literal host, and a proxy bypasses the resolver entirely. It now sits under the mediator's forwarding client, with a TLS-validation hard gate in release builds. Companion: **public-hosts-only DID resolution** by default for did:web and did:webvh, with one `HostPolicy::AllowPrivate` setting for local stacks. +- **`net-guard`** in [[vti-didcomm-js]] 0.8.0 — the browser has no DNS API and cannot inspect a redirect, so "which hosts may this client ever dial" is decided by literal before the fetch: `assertSafeEndpoint`, `guardedFetch`, a typed `BlockedEndpointError`, and a `netPolicy { allowInsecure, allowPrivate, allowHosts }` threaded through mediator resolution, auth, the WebSocket session, VTA REST auth and did:webvh resolution (which since 0.9.0 the library fetches itself, through the guard, and checks *before* an inbound frame's `skid` is resolved). 0.10.1 added single-label hosts and decimal/hex IP spellings. The [[vta-browser-plugin]] and [[openvtc]]'s `health` command use the same posture: mediator endpoints from DID documents are **host-restricted**, and the remaining browser gap — a public name resolving to a private address — waits on a pinned `allowHosts` list. ## DIDComm Authentication in VTI @@ -56,8 +79,10 @@ The [[verifiable-trust-agent|VTA]] uses DIDComm for its authentication flow: This provides strong, decentralized authentication without passwords, API keys, or OAuth providers. -## Trust Spanning Protocol (TSP) +## DIDComm and TSP: Two Carriers, Not a Bridge + +The [[affinidi-tdk|Affinidi TDK]] mediator also carries the [[trust-spanning-protocol|Trust Spanning Protocol]], and since mid-2026 the ecosystem's transport preference is **TSP > DIDComm > REST**. Through the Rev 2 era the mediator could *bridge* — re-pack a TSP message as DIDComm and back — so a TSP-only sender could reach a DIDComm-only recipient. **TSP Rev 3 (September 2026) removed the bridge**: Rev 3's ESSR signature makes a bridge "by construction a point where the message is decrypted and re-signed by someone who is not the sender", so it is unimplementable as specified. DIDComm and TSP are now **separate carriers on the same mediator**: same endpoint, same per-DID WebSocket, sniffed apart at ingress, federated across mediators by their own rules (a TSP routed hop is signed by the previous mediator and sealed to the next, so it needs no blind/rewrap choice) — but both ends of any one conversation must be on one protocol, chosen from what the peer's DID document advertises. -The [[affinidi-tdk|Affinidi TDK]] also implements the **Trust Spanning Protocol (TSP)** — a newer, leaner alternative to DIDComm for certain use cases. TSP uses HPKE-Auth encryption and CESR binary encoding, offering a simpler protocol with strong security guarantees. It's specified by the Trust Over IP Foundation and may complement or partially replace DIDComm in future iterations of the ecosystem. +In practice that gives DIDComm three standing jobs: the protocol-message surface on the shared socket (TSP carries Trust Tasks; the split is deliberate), the fallback for any peer that does not advertise `TSPTransport`, and interop with the Aries/Credo world over DIDComm v1. A VTA can run TSP-only, and the stated long-term direction is to deprecate DIDComm — but nothing on that road is scheduled. -See also: [[verifiable-trust-agent]], [[affinidi-tdk]], [[trust-spanning-protocol]] +See also: [[verifiable-trust-agent]], [[affinidi-tdk]], [[trust-spanning-protocol]], [[vti-didcomm-js]], [[vta-browser-plugin]] diff --git a/content/concepts/dtg-credentials-overview.md b/content/concepts/dtg-credentials-overview.md index b921734..ae2b881 100644 --- a/content/concepts/dtg-credentials-overview.md +++ b/content/concepts/dtg-credentials-overview.md @@ -2,67 +2,91 @@ title: "DTG Credential Types" type: concept tags: [credentials, dtg, trust-graph, trust-over-ip] -date-updated: 2026-08-19 -sources: [dtg-credential-spec, dtg-credentials] +date-updated: 2026-09-18 +sources: [dtg-credential-spec, dtg-credentials, openvtc, verifiable-trust-infrastructure] --- # DTG Credential Types -The Decentralized Trust Graph (DTG) is populated by a family of [[verifiable-credentials|Verifiable Credential]] types, each representing a different kind of trust relationship. These are defined by the Trust Over IP Foundation's DTG Working Group Credentials Task Force in the [[dtg-credential-spec|DTG Core Credentials specification]] (v1.0 Working Draft 01 since July 2026) and implemented in the [[dtg-credentials|dtg-credentials]] library (0.2.0 tracks WD01). +The Decentralized Trust Graph (DTG) is populated by a family of [[verifiable-credentials|Verifiable Credential]] types, each representing a different kind of trust relationship — and, since September 2026, a different kind of *permission*. They are defined by the Trust Over IP Foundation's DTG Working Group Credentials Task Force in the [[dtg-credential-spec|DTG Core Credentials specification]] and implemented in the [[dtg-credentials|dtg-credentials]] library. -All DTG credentials share a common W3C VC format with JSON-LD contexts, signed using EdDSA JCS 2022 Data Integrity Proofs. They use the `https://firstperson.network/credentials/dtg/v1` JSON-LD context. Both W3C VC v2.0 (primary) and v1.1 (legacy) are supported. +Two spec states matter as of 2026-09-18: -The spec (v1.0 Working Draft 01, July 2026) organizes its six credential types into three [[credential-categories|descriptive functional categories]]: Edge, Invitation, and Annotation. These categories are for conceptual understanding only — they don't appear in credential schemas. +- **Working Draft 02** (tagged 2026-09-07) — **eight types** in three [[credential-categories|descriptive categories]] (Edge, Invitation, Annotation) plus the VAC outside them. This is what `dtg-credentials` 0.7.0–0.9.1 implements. +- **`main`, heading for Working Draft 0.4.0** (as of 2026-09-15) — **seven types**: the [[statement-credential|VSC]] absorbs the VEC and VWC as predicate profiles, the VIC is promoted out of its single-member category, leaving two categories (Edge, Annotation) with the VIC and VAC standing apart. Not yet implemented anywhere. -DTG credentials MAY be presented using standard W3C VC presentation methods, but they SHOULD be presented as [[zero-knowledge-proofs|zero-knowledge proofs]] whenever privacy preservation is desired — and implementations SHOULD make ZKP presentation the default. The spec defines two ZKP constructions for Edge Credentials: a **pairwise** construction anchored to the VRC, and a **community-anchored** construction anchored to the VMC. See [[zero-knowledge-proofs]]. +All DTG credentials share a common W3C VC format with JSON-LD contexts (`https://firstperson.network/credentials/dtg/v1`), signed with Data Integrity proofs — EdDSA JCS 2022 in the library, and since VTI #1553/#1557 the VTC issues **hybrid multi-proof credentials** (Ed25519 + ML-DSA). W3C VC v2.0 is primary, v1.1 legacy. Every type may carry `taskContext` ([[trust-task-context-binding]]). Where one credential references another — the VMC acknowledgement, the VWC, VDC and VAC chains — it does so by **`digestMultibase`**: SHA-256 over the JCS form excluding `proof`, as a base58btc multihash (WD02 replaced WD01's `sha256:`). Identifiers are no longer typed (no C/M/R/P-DID); each carries a holder-declared [[correlation-scope]] instead. -## The Credential Types +DTG credentials MAY be presented as ordinary VCs but SHOULD be presented as [[zero-knowledge-proofs|zero-knowledge proofs]] whenever privacy matters, by default. -### Membership Credential (VMC) — Edge -**"This entity is a member of community X."** +## Edge credentials — bidirectional, the second half is consent -A [[membership-credential|Membership Credential]] attests to the membership of an entity in a [[verifiable-trust-community|VTC]] or [[verifiable-trust-network|VTN]]. Membership is verified through a **bi-directional pair of VMCs** — one from the community to the member, and one back. The issuer is a [[did-types|C-DID]]; the subject is an [[did-types|M-DID]] (or another C-DID for VTN↔VTC membership). When the issuing community's governance enforces personhood guarantees, the VMC qualifies as a [[personhood-credential|Personhood Credential (PHC)]] — determined by [[trust-registries|trust registries]], not credential structure. +### Membership Credential (VMC) +**"This entity is a member of community X" — and "I agree that I am."** -### Relationship Credential (VRC) — Edge +A [[membership-credential|Membership Credential]] attests to membership in a [[verifiable-trust-community|VTC]] or [[verifiable-trust-network|VTN]]. WD02 finally specified both halves: the community-issued **grant** (VTC → member) and the member-issued **acknowledgement** (member → VTC, carrying `digestMultibase` of the grant), the member's consent artifact without which a community cannot prove anyone's membership. When the community's governance enforces personhood and one-membership-per-person, the grant is a [[personhood-credential|PHC]] — a matter for [[trust-registries|trust registries]], not schema. A [[data-rooms|data room]] uses the same pair to form its membership edge. + +### Relationship Credential (VRC) **"I have a genuine trust relationship with this person."** -A [[relationship-credential|Relationship Credential]] attests to a peer-to-peer relationship between two entities. The relationship is verified through a **bi-directional pair of VRCs** — two VRCs (one each direction) form a complete DTG edge. Each relationship uses a unique [[did-types|R-DID]] for privacy. The issuance follows a multi-step protocol: request → accept → finalize → VRC exchange. See also the [[witnessed-vrc-exchange|Witnessed VRC Exchange Protocol]]. +A [[relationship-credential|Relationship Credential]] attests to a peer-to-peer relationship; two VRCs (one each direction) form a complete edge. A `pairwise` identifier per relationship is RECOMMENDED. WD02's *Edge Verifiability* section defines when a VRC counts as an edge *to a given verifier* — against the VTCs that verifier accepts as anchors, by disclosure or by community-anchored ZKP. See also the [[witnessed-vrc-exchange|Witnessed VRC Exchange Protocol]]. + +### Delegation Credential (VDC) — new in WD02 +**"I appoint this party to act in my name, for these acts, until then."** -### Invitation Credential (VIC) — Invitation -**"I invite this entity to join the trust graph."** +A [[delegation-credential|Delegation Credential]] is a grant from delegator to delegate plus the delegate's **required acceptance**. It establishes representation, never authority: the verifier substitutes the delegator and asks whether *they* may act; reach is the intersection. Single-hop by default (`maxDepth`), `validUntil` required, status conditional, not a bearer token. This is how a person appoints an AI agent to act *as them*. -An [[invitation-credential|Invitation Credential]] authorizes onboarding of a new member into a VTC or VTN. The issuer can be the community's C-DID or an authorized member's M-DID, depending on community policy. +## Annotation credentials — attach data, create no structure -### Persona Credential (VPC) — Annotation +### Persona Credential (VPC) **"I am revealing a persona identity to you."** -A [[persona-credential|Persona Credential]] links a [[did-types|P-DID]] (Persona DID) to an existing relationship. It enables selective persona disclosure — including the "Banksy Maneuver" (proving you control a pseudonymous identity to a specific person). +A [[persona-credential|Persona Credential]] links a persona — an identifier ordinarily declared `directed` — to an existing relationship, enabling the "Banksy Maneuver". VPC issuance shipped in VTI #1074. -### Endorsement Credential (VEC) — Annotation +### Endorsement Credential (VEC) **"I endorse this person's competency in X."** -An [[endorsement-credential|Endorsement Credential]] lets one person vouch for another's skills or attributes. Includes a required `endorsement` object with community/VTN-defined structure (type, name, competency level). +An [[endorsement-credential|Endorsement Credential]] lets one party vouch for another's skills or attributes, with a community-defined `endorsement` payload. WD02 stresses it is a statement *about* a party, never a permission — that is the VAC's job. [[openvtc|OpenVTC]] uses a revocable `CommunityRole` VEC to name vetters in [[peer-identity-vetting]]. On `main` the VEC is the `dtg:endorses` profile of the VSC. + +### Witness Credential (VWC) +**"I witnessed that this specific edge was established, in this exchange."** + +A [[witness-credential|Witness Credential]] is a third-party attestation by a person or a VTA applying community witnessing policy. `taskContext` and `digestMultibase` (of the witnessed edge credential — a VRC *or* a VMC since WD02) are REQUIRED; `credentialSubject.id` is the issuer of that credential; one VWC per direction. On `main` the VWC is the `dtg:witnessed` profile of the VSC, with direction binding made unconditional. + +### Statement Credential (VSC) — `main` only +**"I state that ⟨predicate⟩ holds of this node."** + +The [[statement-credential|Statement Credential]] is one type carrying subject, an absolute-IRI `predicate` from a governed vocabulary, and an `object`. Verifiers fail closed on predicates they are not configured for; a VSC *attests* and never *establishes*. Not in WD02, not implemented. + +## Outside the categories + +### Invitation Credential (VIC) +**"I invite this entity to join."** + +An [[invitation-credential|Invitation Credential]] authorizes onboarding into a VTC or VTN (two variants by issuer/subject rules). In WD02 its own "Invitation" category; on `main` a top-level section, since it bootstraps a node into a community rather than forming or annotating an edge. Roles and access do *not* belong in a VIC — that is the VAC. A [[data-rooms|data room]] admits members by VIC. -### Witness Credential (VWC) — Annotation -**"I witnessed that this relationship is genuine."** +### Authority Credential (VAC) — new in WD02 +**"You may do these actions at this scope, as yourself."** -A [[witness-credential|Witness Credential]] is a third-party attestation — by a person, or by a VTA applying a community's witnessing policy. Since WD01 it MUST carry `taskContext` (the exchange it was issued in — see [[trust-task-context-binding]]) and a required `digest` of the specific VRC it attests (SHA-256 over the VRC's JCS canonical form, encoded `sha256:`); `credentialSubject.id` is the issuer of that VRC, and a witnessed bidirectional exchange yields one VWC per direction. An optional `witnessContext` object carries event, session ID, and verification method (e.g., "in-person-proximity"). See [[witnessed-vrc-exchange]] for the full protocol. +An [[authority-credential|Authority Credential]] confers permission within a governed `scope` as an explicit `actions` list, and can be **attenuated** by its holder — an agent gets four hours of read-only, not its principal's standing authority — with the verifier walking the whole holder-presented chain (digest `parent`, depth ≤ 8). `validUntil` required. On `main`: not a bearer credential (key control at invocation, `audience` removed), `maxAttenuation`, and revocation that cascades. The permission model of [[data-rooms]] (`read`/`write`/`curate`/`admin`). ### Relationship card (r-card) — a companion Verifiable Data Structure, not a credential **"Here is my contact information."** -An r-card carries vCard/jCard contact information (per RFC 7095) in a verifiable wrapper — "a modern, self-updating vCard," typically exchanged alongside VRCs. It is **not a DTGCredential** and, since WD01, is no longer defined in the core spec at all: it moves to a planned *DTG Core Verifiable Data Structures* companion (with an agent card). The [[dtg-credentials]] crate keeps deprecated RCard types for now. +vCard/jCard data in a verifiable wrapper, exchanged alongside VRCs; removed from the core spec in WD01 and bound for the planned *DTG Verifiable Data Structures* companion (with an agent card). The crate keeps deprecated RCard types. ## How They Fit Together -In practice, a participant's trust profile might look like: +A participant's trust profile might look like: -1. They hold **VMCs** proving membership (and personhood) in two VTCs within a VTN -2. They have **VRCs** with 10 people they know personally (20 credentials — one each direction, forming 10 complete edges) -3. Several counterparties have issued **VECs** endorsing their development skills -4. Three of their relationships have **VWCs** from a [[witnessed-vrc-exchange|witnessed exchange]] at a conference -5. They've shared a **VPC** with a trusted contact, linking a pseudonymous identity +1. Two **VMC pairs** proving (consented) membership — and personhood — in two VTCs within a VTN +2. **VRCs** with 10 people they know (20 credentials, 10 complete edges), each under its own `pairwise` identifier +3. Several **VECs** endorsing their development skills +4. Three relationships carrying **VWCs** from a [[witnessed-vrc-exchange|witnessed exchange]] at a conference +5. A **VPC** shared with one trusted contact, linking a pseudonymous persona +6. A **VAC** from a data room for `read`/`write`, attenuated to `read`-for-four-hours for their agent +7. A **VDC** appointing an assistant service to schedule in their name -Anyone evaluating this participant can traverse these credentials to build a trust assessment — following multiple independent paths, checking witness attestations, and verifying every signature along the way. +Anyone evaluating this participant can traverse these credentials — following independent paths, checking witness attestations, walking authority chains back to the governing party — and verify every signature along the way. The [[verifiable-trust-infrastructure|VTI]] draws the resulting graph, distinguishing half-edges from complete edges (VTI #1073, #1213). -See also: [[credential-categories]], [[decentralized-trust-graph]], [[verifiable-credentials]], [[dtg-credentials]] +See also: [[credential-categories]], [[decentralized-trust-graph]], [[verifiable-credentials]], [[dtg-credentials]], [[correlation-scope]], [[data-rooms]] diff --git a/content/concepts/endorsement-credential.md b/content/concepts/endorsement-credential.md index 25d00d1..890ee09 100644 --- a/content/concepts/endorsement-credential.md +++ b/content/concepts/endorsement-credential.md @@ -1,24 +1,37 @@ --- title: "Endorsement Credential (VEC)" type: concept -tags: [credentials, dtg, endorsement, skills] -date-updated: 2026-04-09 -sources: [dtg-credentials] +tags: [credentials, dtg, endorsement, skills, roles] +date-updated: 2026-09-18 +sources: [dtg-credential-spec, dtg-credentials, openvtc, verifiable-trust-infrastructure] --- # Endorsement Credential (VEC) -A Verifiable Endorsement Credential lets one participant vouch for another's skills, competencies, or attributes. Unlike a [[relationship-credential|Relationship Credential]] (which says "I know this person"), an endorsement says "I can attest to this person's ability in X." +A Verifiable Endorsement Credential lets one participant vouch for another's skills, competencies, or attributes. Unlike a [[relationship-credential|Relationship Credential]] (which says "I know this person"), an endorsement says "I can attest to this person's ability in X." It is an [[credential-categories|Annotation Credential]]: it attaches a claim to a party already in the [[decentralized-trust-graph|graph]] and creates no structure. -## What It Contains +## What It Contains (Working Draft 02) -Beyond the standard VC fields, an endorsement includes: -- **Endorsement type** — e.g., `SkillEndorsement` -- **Competency description** — what skill or attribute is being endorsed -- **Competency level** — optional proficiency rating +- `type` includes `EndorsementCredential` +- `issuer` — the endorser; `credentialSubject.id` — the endorsed party +- `credentialSubject.endorsement` — REQUIRED, a community- or VTN-defined structure; the spec's example is `{ "type": "SkillEndorsement", "name": "Software Development", "competencyLevel": "expert" }` + +The [[dtg-credential-spec|spec]] is careful about the word *verifiable*: it applies to the cryptographic assurance of the issuer's signature, **not to the truth of the assertion**, which must be weighed independently. The vocabulary of what may be endorsed, and what weight it carries, is defined by the governing [[verifiable-trust-community|VTC]] or [[verifiable-trust-network|VTN]]. + +## What a VEC Is Not: a Permission + +WD02 added the [[authority-credential|Verifiable Authority Credential]] precisely because implementations had been packing "may write" into endorsements. The spec now draws the line: an endorsement is a statement *about* a party — skilled, trusted, of good standing — that a verifier decides for itself what to do with; a VAC is a statement *to* a verifier, where the party governing the scope has already decided. "Conflating the two puts a decision that belongs to the governing party into a claim that reads as reputation, and leaves verifiers to infer permission from adjectives." A VEC can be *evidence* a governing party weighs before issuing a VAC; it never confers authority. + +## Where It Is Going — `main` (Working Draft 0.4.0, not yet tagged or implemented) + +On the spec's `main` branch (2026-09-15) the VEC stops being a concrete type and becomes the **`dtg:endorses` predicate profile** of the [[statement-credential|Verifiable Statement Credential]]: `type` is `StatementCredential`, `credentialSubject.predicate` is `https://firstperson.network/credentials/dtg/v1#endorses`, and the endorsement payload moves to `credentialSubject.object.value`. The profile classifies an endorsement as **evidence**, keeps `taskContext` OPTIONAL and the issuer's scope unconstrained, and states what verification establishes — that the issuer endorsed the subject with that payload — and does not: that the endorsement is accurate, that the issuer is qualified to give it, or that the subject holds any membership, authority or status. Communities may define their own endorsement-like predicates under namespaces they control. ## Use in the Trust Graph -Endorsements add qualitative depth to the [[decentralized-trust-graph|Decentralized Trust Graph]]. A VRC tells you two people are connected; an endorsement tells you *what* one trusts the other to do. For the Know Your Developer use case, endorsements are how technical competence gets verified — not by a certification authority, but by peers who've worked with you. +Endorsements add qualitative depth. A VRC tells you two people are connected; an endorsement tells you *what* one trusts the other to do. For the Know Your Developer use case, endorsements are how technical competence gets verified — not by a certification authority, but by peers who've worked with you. + +## In OpenVTC: role endorsements + +[[openvtc|OpenVTC]] and the [[verifiable-trust-infrastructure|VTI]] use the VEC for **community roles**: the VTC admin console issues a member's VMC together with a role VEC, and the Dogwood-era **peer identity vetting** design (openvtc #292–#343, design DRAFT v3) names vetters by a **revocable `CommunityRole` VEC**, with a vetter directory and QR tickets built on it — see [[peer-identity-vetting]]. Under `main`'s model this is exactly the kind of statement a community would issue under a `dtg:endorses` or community-defined predicate: it is evidence of a role the community has granted, and the community's own policy — not the credential — decides what a vetter may do. -See also: [[dtg-credentials-overview]], [[relationship-credential]], [[decentralized-trust-graph]] +See also: [[dtg-credentials-overview]], [[relationship-credential]], [[authority-credential]], [[statement-credential]], [[decentralized-trust-graph]], [[peer-identity-vetting]] diff --git a/content/concepts/first-person-network.md b/content/concepts/first-person-network.md index 7db27a3..2756e34 100644 --- a/content/concepts/first-person-network.md +++ b/content/concepts/first-person-network.md @@ -2,8 +2,8 @@ title: "The First Person Network" type: concept tags: [first-person, vision, protocol, identity] -date-updated: 2026-08-19 -sources: [openvtc, verifiable-trust-infrastructure, dtg-credentials, verifiable-git-infrastructure] +date-updated: 2026-09-18 +sources: [openvtc, verifiable-trust-infrastructure, dtg-credentials, verifiable-git-infrastructure, vtafarm] --- # The First Person Network @@ -15,7 +15,7 @@ The First Person Network is the overarching vision that the OpenVTC ecosystem im The name is deliberate: - **First person** — "I am" rather than "they say I am" - You create your own identity ([[decentralized-identifiers|DID]]) -- You host it on the domain of your choice +- You host it on the domain of your choice — or on a hosted [[vtafarm|VTA Farm]] that holds your agent but never your admin key - You build trust through real relationships, not institutional endorsements - No organization can revoke your identity or gatekeep your participation @@ -35,7 +35,7 @@ The key distinction from general self-sovereign identity (SSI) is the emphasis o The First Person Protocol defines how participants: 1. Create and host their Persona DID (using [[did-webvh]]) -2. Establish private communication channels ([[didcomm]]) +2. Establish private communication channels ([[trust-spanning-protocol|TSP]], with [[didcomm]] for interop) 3. Exchange [[personhood-credential|Personhood Credentials]] and [[relationship-credential|Relationship Credentials]] 4. Build and traverse the [[decentralized-trust-graph|Decentralized Trust Graph]] 5. Form [[verifiable-trust-community|Verifiable Trust Communities]] @@ -48,6 +48,6 @@ This challenge has intensified with the rise of AI agents. An AI can now convinc The First Person Network answers this by letting developers build verifiable trust graphs through their real professional relationships. Instead of trusting a GitHub account, you can verify a chain of peer attestations, endorsements, and witness proofs — all anchored in communities that have verified their members' personhood. -Since July 2026 this has a concrete, shippable form: [[verifiable-git-infrastructure|Verifiable Git Infrastructure (VGI)]]. A developer signs commits with a key held in their [[verifiable-trust-agent|VTA]] (`did-git-sign`), the commit names its signer by DID, and a project's CI (`verify-trust`) asks the community's [[trust-registries|Trust Registry]] whether that DID is authorised to sign for this repository *right now* — failing closed. "VGI verifies, the VTC decides": remove a contributor from the registry and their next commit fails everywhere at once. The human side of the same story is **agent names** — `example.com/@alice` instead of a DID string — so a reviewer sees a name that is cryptographically claimed by the DID document, not a display name anyone could type. +Since July 2026 this has a concrete, shippable form: [[verifiable-git-infrastructure|Verifiable Git Infrastructure (VGI)]]. A developer signs commits with a key held in their [[verifiable-trust-agent|VTA]] (`did-git-sign`), the commit names its signer by DID — since the Dogwood release (VGI 0.4.7) as a `Signed-by-DID:` trailer written by a commit hook, so `user.email` stays an ordinary address and forges still attribute the commit — and a project's CI (`verify-trust`) asks the community's [[trust-registries|Trust Registry]] whether that DID is authorised to sign for this repository *right now* — failing closed. "VGI verifies, the VTC decides": remove a contributor from the registry and their next commit fails everywhere at once. The human side of the same story is **agent names** — `example.com/@alice` instead of a DID string — so a reviewer sees a name that is cryptographically claimed by the DID document, not a display name anyone could type. And since September 2026 the human root of all of this — the moment a community checks that a new member is a real person — is being made explicit as [[peer-identity-vetting]]. See also: [[decentralized-trust-graph]], [[verifiable-trust-community]], [[openvtc]] diff --git a/content/concepts/invitation-credential.md b/content/concepts/invitation-credential.md index fa4c8a1..76bccaa 100644 --- a/content/concepts/invitation-credential.md +++ b/content/concepts/invitation-credential.md @@ -2,7 +2,7 @@ title: "Invitation Credential (VIC)" type: concept tags: [credentials, dtg, invitation, onboarding] -date-updated: 2026-08-19 +date-updated: 2026-09-18 sources: [dtg-credential-spec, openvtc, verifiable-trust-infrastructure] --- @@ -14,21 +14,23 @@ A Verifiable Invitation Credential authorizes the onboarding of a new member int The issuer of a VIC depends on community policy: -- **For VTC membership**: the VTC's [[did-types|C-DID]], or an authorized member's M-DID (if the community allows member-initiated invitations) -- **For VTN membership**: the VTN's C-DID, or a member VTC's C-DID +- **For VTC membership**: the VTC's own DID, or an authorized member's DID (if the community allows member-initiated invitations) +- **For VTN membership**: the VTN's DID, or a member VTC's DID -This flexibility lets communities choose between centralized invitation (only the community itself can invite) and decentralized invitation (existing members can bring in new people). Working Draft 01 of the [[dtg-credential-spec|spec]] makes explicit that these are **two functional variants of one credential** — the *VTC invitation credential* (issued to the prospect's M-DID) and the *VTN invitation credential* (issued to a VTC's C-DID) — distinguished by issuer/subject rules, not by separate type strings. Its new Security Considerations add replay guidance: VICs should be issued with **short validity periods** and treated as **single-use** by the accepting VTA / Policy Enforcement Point (PEP — the VTC's policy engine for credential issuance and revocation). An open editor's note asks whether role information should be embedded in the VIC. +This flexibility lets communities choose between centralized invitation (only the community itself can invite) and decentralized invitation (existing members can bring in new people). The [[dtg-credential-spec|spec]] makes explicit that these are **two functional variants of one credential** — the *VTC invitation credential* (issued to the identifier the prospect proposes to use in that community) and the *VTN invitation credential* (issued to a prospective member VTC's identifier) — distinguished by issuer/subject rules, not by separate type strings. (WD01 said "M-DID" and "C-DID"; WD02 retired those types in favour of [[correlation-scope]].) Its Security Considerations add replay guidance: VICs should be issued with **short validity periods** and treated as **single-use** by the accepting VTA / Policy Enforcement Point (PEP — the VTC's policy engine for credential issuance and revocation). -The spec also sketches the identity-proofing path a VIC can start: the VTC issues a VIC to the prospect's M-DID → the prospect proves themselves to an **identity verification provider** (IDVP — Veriff, Jumio, Yoti and the like are the examples given) → the IDVP issues an **identity verification credential** (IDVC, not a DTG credential) to that M-DID, which the community's join policy can then require. +WD01's open editor's note — should role or access-control information be embedded in the VIC? — was answered in WD02 (spec PR #29): **it should not.** What a party may *do* is conferred by an [[authority-credential|Authority Credential]], which can be re-issued or attenuated without touching the invitation that admitted them; roles and policy are otherwise inferred from the issuer plus the [[trust-registries|trust registry]]. + +The spec also sketches the identity-proofing path a VIC can start: the VTC issues a VIC to the prospect's proposed identifier → the prospect proves themselves to an **identity verification provider** (IDVP — Veriff, Jumio, Yoti and the like are the examples given) → the IDVP issues an **identity verification credential** (IDVC, not a DTG credential) to that same identifier, which the community's join policy can then require. ## Role in the Ecosystem -VICs are [[credential-categories|Invitation Credentials]] — they bootstrap new participants but don't create persistent graph structure. Once the invitee completes onboarding and receives a [[membership-credential|Membership Credential (VMC)]], the invitation has served its purpose. +In Working Draft 02 the VIC was the sole member of an "Invitation Credentials" [[credential-categories|category]]; on the spec's `main` branch (2026-09-10, closing issue #28) it was **promoted to a top-level section standing outside the categories**, alongside the VAC — it bootstraps a node *into* a community rather than forming an edge or annotating one. Nothing normative changed. Once the invitee completes onboarding and the [[membership-credential|VMC]] pair is formed, the invitation has served its purpose. A [[data-rooms|data room]] — a DTG node with its own DID — admits a member the same way: VIC for entry, VMC pair to form the membership edge, then [[authority-credential|VACs]] for what the member may do inside. The [[verifiable-trust-agent|VTA/PEP]] processes invitation credentials during the onboarding flow. ## Implementation Status -As of June 2026 the VIC is **implemented end-to-end**, making it the first invitation mechanism to go from spec to working code; by August (OpenVTC v0.3.x / the `Cypress` release) a pasted VIC pre-fills the community in the join flow, the join can run over TSP, and an applicant can poll a community about an unanswered join. On the community side, the VTC service supports automatic join via VIC — with role-on-invite, revocation, QR-sized invitations in the admin UI, and a verdict-model admission pipeline. On the member side, [[openvtc|OpenVTC]] presents a VIC at join, stores VICs in the VTA credential vault, and supports a **subject-linkage proof** so an invitee can join under a freshly minted DID different from the one the VIC names. See the Recent Development sections on [[openvtc]] and [[verifiable-trust-infrastructure]]. +As of June 2026 the VIC is **implemented end-to-end**, making it the first invitation mechanism to go from spec to working code; by August (OpenVTC v0.3.x / the `Cypress` release) a pasted VIC pre-fills the community in the join flow, the join can run over TSP, and an applicant can poll a community about an unanswered join. On the community side, the VTC service supports automatic join via VIC — with role-on-invite, revocation, QR-sized invitations in the admin UI, and a verdict-model admission pipeline. On the member side, [[openvtc|OpenVTC]] presents a VIC at join, stores VICs in the VTA credential vault, and supports a **subject-linkage proof** so an invitee can join under a freshly minted DID different from the one the VIC names. Since Dogwood a pasted VIC must carry the DTG common structure at ingest (openvtc #256), and the join now completes properly: receiving the community's VMC grant auto-issues the member's acknowledgement, closing the membership edge (openvtc #259–#267). See the Recent Development sections on [[openvtc]] and [[verifiable-trust-infrastructure]]. -See also: [[membership-credential]], [[credential-categories]], [[verifiable-trust-community]], [[verifiable-trust-network]] +See also: [[membership-credential]], [[authority-credential]], [[credential-categories]], [[verifiable-trust-community]], [[verifiable-trust-network]], [[data-rooms]] diff --git a/content/concepts/membership-credential.md b/content/concepts/membership-credential.md index 139cbdd..e029ae5 100644 --- a/content/concepts/membership-credential.md +++ b/content/concepts/membership-credential.md @@ -1,40 +1,53 @@ --- title: "Membership Credential (VMC)" type: concept -tags: [credentials, dtg, membership, community, personhood, edge, zkp] -date-updated: 2026-08-19 -sources: [dtg-credential-spec, dtg-credentials] +tags: [credentials, dtg, membership, community, personhood, edge, zkp, consent] +date-updated: 2026-09-18 +sources: [dtg-credential-spec, dtg-credentials, openvtc, verifiable-trust-infrastructure] --- # Membership Credential (VMC) -A Verifiable Membership Credential attests to the **membership of an entity in a community**. It connects a person, device, or agent to a [[verifiable-trust-community|VTC]] — or one community to a [[verifiable-trust-network|VTN]]. +A Verifiable Membership Credential attests to the **membership of an entity in a community**. It connects a person, device, agent or service to a [[verifiable-trust-community|VTC]] — or one community to a [[verifiable-trust-network|VTN]], or a member to a [[data-rooms|data room]]. VMCs are [[credential-categories|Edge Credentials]]. -VMCs are [[credential-categories|Edge Credentials]]. The issuer is a [[did-types|C-DID]] (the community's DID); the subject is an [[did-types|M-DID]] (a member's DID) or another C-DID (when a VTN grants membership to a VTC). +Since Working Draft 02 of the [[dtg-credential-spec|spec]] the identifiers are no longer typed: the issuer of a grant is simply the VTC's own identifier (which can only truthfully be declared `public`), and the member appears under whatever identifier — at whatever [[correlation-scope]] — the member chose for this membership. The old C-DID / M-DID vocabulary is retired ([[did-types]]). -## Bidirectionality: Two VMCs = One Membership Edge +## Two VMCs = One Membership Edge — and the Second One Is Consent -Membership in the DTG is symmetric: it isn't enough for a community to claim a member, or for a member to claim a community. **Membership is verified through a bi-directional pair of VMCs** — one issued by the community to the member, and one issued by the member back to the community. Together the pair forms a complete membership edge between two entities (nodes) in the trust graph. +Membership in the DTG is symmetric: it isn't enough for a community to claim a member. **Membership is verified through a bi-directional pair of VMCs**, and WD02 (spec PR #12, 2026-08-28) finally made both halves constructible after a real contradiction — five places said "two VMCs form an edge" while the schema only allowed the community-issued one: -This mirrors the bidirectional model used for [[relationship-credential|Relationship Credentials (VRCs)]]: in the DTG, edges are always two-sided, attested by both ends. +| | `issuer` | `credentialSubject.id` | `digestMultibase` | +|---|---|---|---| +| **Community-issued VMC — the grant** | the VTC / VTN | the member | MUST be absent | +| **Member-issued VMC — the acknowledgement** | the member, from the same identifier the grant named | the VTC / VTN | REQUIRED: digest of the grant | -## Dual Purpose +The two directions are distinguished by these rules and by the presence of the digest, not by separate type strings (where both ends are communities, as in VTN membership, the digest is the discriminator). The digest is SHA-256 over the grant's JCS form *excluding `proof`*, as a base58btc multihash — so a re-signed grant with identical claims still satisfies an existing acknowledgement, while a re-issued grant with different claims does not, forcing re-acknowledgement. + +**Why a pair.** The acknowledgement is the member's **consent artifact**. A community can always issue a credential naming someone as a member, but it cannot produce the acknowledgement without that party's signature. So: the grant MUST be issued first; an acknowledgement whose digest matches no valid grant MUST NOT complete the edge; and whenever anyone *other than the member* asserts that an entity is a member, the verifier MUST require the acknowledgement — a community asserting a membership MUST be able to produce it. Unconsented membership claims become unprovable, and a community that cannot show an acknowledgement is visibly asserting a membership nobody agreed to. -VMCs serve two functions: +The rule is deliberately direction-sensitive: a member presenting *their own* grant, with proof of control of the subject key, MAY be accepted alone — the presentation is itself the participation. This is what keeps the community-anchored ZKP and the [[personhood-credential|PHC]] constructions working, since both rest on the member holding the grant. -1. **Community membership** — proving you belong to a specific community, enabling community-level policies (who can endorse, what trust thresholds apply) -2. **Personhood attestation** — when the issuing community's governance enforces real human personhood and one-membership-per-person rules, the VMC qualifies as a [[personhood-credential|Personhood Credential (PHC)]] +Lifecycle (withdrawal, revocation, re-issuance, and where a verifier learns the status of a *member-issued* VMC, which must be under the member's control) is deferred to the planned Trust Task Protocols spec. Two interim controls follow from the digest binding: a short `validUntil` on the grant forces periodic re-acknowledgement; a short `validUntil` on the acknowledgement bounds how long the community holds a presentable proof of consent — because the acknowledgement is also a **disclosure artifact** the community can show to third parties without the member's involvement. -Whether a VMC is a PHC is determined by the community's [[trust-registries|trust registry]], not by any structural difference in the credential. See [[personhood-credential]] for details. +## What Has Members + +A WD02 editor's note fixes a boundary: **a VMC binds a member to a node that has members** — a VTC, a VTN, a service with registered clients, a device with authorised identities. A person is not a collective; "membership in a person" is a [[relationship-credential|VRC]] (peers) or a [[delegation-credential|VDC]] (one acts in the other's name), never a VMC. + +## Dual Purpose + +1. **Community membership** — belonging to a specific community, enabling community-level policies +2. **Personhood attestation** — when the issuing community's governance enforces real human personhood and one-membership-per-person, the *grant* qualifies as a [[personhood-credential|PHC]]. Whether or not the member has acknowledged it; the acknowledgement gates only the community's ability to assert that membership to others. Determined by the [[trust-registries|trust registry]], not by structure. ## VTN Membership -VMCs also express the relationship between [[verifiable-trust-network|VTNs]] and VTCs. A VTN issues a VMC to a VTC's C-DID, and the VTC issues a VMC back, proving the community is a recognized member of the network. This enables hierarchical trust structures. +A VTN issues a grant to a VTC's identifier, and the VTC acknowledges back — the same pair, same rules. Since WD02's *Edge Verifiability* section, a VTN is one example of the anchor set a verifier accepts, not a precondition for an edge to exist. ## Community-Anchored ZKP -The VMC is the anchor for the **community-anchored ZKP** construction defined in the spec's VMC section. When both parties to a [[relationship-credential|VRC]] hold VMCs from the same community, the holder can prove the existence of a relationship *within the community's governance context* — without revealing the specific DIDs or other credential details. When the community's VMCs qualify as [[personhood-credential|PHCs]], that personhood assurance carries forward into every relationship proven through this construction. +The VMC anchors the **community-anchored ZKP**: when both parties to a VRC hold VMCs from the same community, the holder proves possession of the VRC, possession of their own grant, and that the counterparty's grant is from the same community — without revealing identifiers. WD02 is candid that the third statement currently establishes only that the community *attested* the counterparty's membership, not that the counterparty acknowledged it, and that under strict `pairwise` scopes the proof must additionally establish common control of two distinct identifiers. See [[zero-knowledge-proofs]]. + +## Implementation Status -This is one of the two ZKP constructions defined for [[credential-categories|Edge Credentials]]; the other is the pairwise construction anchored to the VRC. See [[zero-knowledge-proofs]] for the full picture, including how to choose between them. +The pair was harder to ship than to specify. In [[openvtc|OpenVTC]] the member→community half **had never actually landed** before Dogwood: VMCs had no `id`, [[dtg-credentials]] 0.2.0 had no field for one, and the VTC's rejection of the reciprocal came back as an uncorrelated problem-report the client discarded — silent on both sides. It was fixed across openvtc #259–#267 and `dtg-credentials` 0.3.0 (`with_id`), then 0.4.0 (`CredentialSubjectMembership`, `new_member_vmc`, `acknowledges()`) and 0.5.0 (digest the *received* grant JSON, not a re-serialisation). Receiving a grant now auto-issues the reciprocal and closes the join; 0.10.0's `new_member_vmc_for` refuses a grant that does not name the party answering it. On the VTI side, #1073 distinguishes half-edges from complete edges and #1213 (the Dogwood tag commit) draws the membership edge on the graph; the VTC issues hybrid multi-proof VMCs (Ed25519 + ML-DSA) since #1553/#1557. -See also: [[zero-knowledge-proofs]], [[personhood-credential]], [[trust-registries]], [[verifiable-trust-community]], [[verifiable-trust-network]], [[credential-categories]] +See also: [[zero-knowledge-proofs]], [[personhood-credential]], [[trust-registries]], [[verifiable-trust-community]], [[verifiable-trust-network]], [[credential-categories]], [[correlation-scope]], [[data-rooms]] diff --git a/content/concepts/peer-identity-vetting.md b/content/concepts/peer-identity-vetting.md new file mode 100644 index 0000000..1e5f627 --- /dev/null +++ b/content/concepts/peer-identity-vetting.md @@ -0,0 +1,39 @@ +--- +title: "Peer Identity Vetting" +type: concept +tags: [vetting, personhood, admission, community, governance, endorsement, trust-tasks] +date-updated: 2026-09-18 +sources: [openvtc, verifiable-trust-infrastructure, verifiable-trust-agent, dtg-credentials] +--- + +# Peer Identity Vetting + +## Why: People Are the Root of Personhood + +A [[verifiable-trust-community|community]] that admits anyone on request is only as trustworthy as its weakest signup form. The communities this ecosystem is built for — the Linux kernel's maintainers are the worked example — have always solved that the same way: **existing members check who you are before you get in.** The kernel does it with a PGP web of trust: a key signed by two or three account holders who met you, a trust path to Linus, key-signing parties. It works, and its pain points are known — a public list of names and coordinates, a published graph of who vouched for whom, in-person bias, and a strong set that shrank from 358 to 94 keys when GnuPG dropped SHA-1 signatures. + +Peer identity vetting is the ecosystem's replacement for that model. Its premise is that the *human* root of [[personhood-credential|personhood]] is not a document check by a machine but **admission by people who checked you** — and that this can be made into evidence a community can count, under rules it publishes, without publishing who vouched for whom. + +## How It Works + +An **applicant** who wants to join a vetting community, from [[openvtc]] alone: + +1. reads the community's **vetting requirements** from its join manifest before disclosing anything — how many statements, by which methods (`inPerson`, `video`, `priorAcquaintance`), which claims, how fresh, how independent; +2. builds a signed **Vetting Card** from one of their persona *faces* (a profile over their attribute pool), bound to the DID they will join with; +3. reaches a **vetter** — a member the community has named — by presenting a **ticket** the vetter issued (an 8-character Crockford code or a QR), the anti-spam gate: a wrong code gets no answer; +4. sits a **vetting session**, in person or on video. Both screens derive the same spoken **match code** from the session document, under the domain tag `vetting-session-match/v1`; reading it aloud proves the person in the room is driving the client that controls the join DID. The vetter checks the card against whatever documentation *they* accept, and issues back a **Vetting Statement** — a DTG [[endorsement-credential|Endorsement Credential]] of type `IdentityVetting`, bound to the session by [[trust-task-context-binding|`taskContext`]]; +5. repeats until the checklist fills, then submits the statements in one presentation. The VTC verifies them, counts distinct vetters *by member record* (independence must be shown by evidence, not by DID), evaluates governance policy and admits, asks for more, refers to a moderator, or denies. + +Vetters are named by a revocable **`CommunityRole: vetter`** credential the community issues, with a status list and a validity of up to two years. Every acceptance carries it as an `eligibilityVp`, so the applicant knows *before* the session that the statement will count — and the [[trust-registries|role]] is a policy input, not a graph computation: eligibility rules can look at tenure, roles and **depth** from the founding anchors, so the kernel's "trust path ≤ 5 hops" becomes one line of Rego. Depth and lineage are recorded privately and never published. + +## What It Relates To + +- **[[personhood-credential|Personhood]].** Vetting grew out of the personhood work. In August 2026 VTI let a community vet a member in person and have it count as personhood evidence (#1085 — a community acting as its own identity-verification provider, with no new credential type) and moved the personhood ceremony onto messaging with its challenge finally *signed* (#1086); OpenVTC's #257 gave that ceremony its spoken eight-character match code. Peer vetting reuses the same construction under its own domain tag, so the two ceremonies never read out the same code. +- **[[membership-credential|Membership]].** Vetting is a *way of joining*, not a step before it: OpenVTC offers every way in — [[invitation-credential|invitation]], vetting, open request — and an admitted application ends in the ordinary VMC grant, the reciprocal VMC, and any role credentials. After admission the kernel payoff is `git-trust/grant` and [[verifiable-git-infrastructure|signed commits]] checked in CI. +- **[[trust-registries|Roles and policy]].** The vetter role, the statement type, the admission criteria and the eligibility rules are all community governance, evaluated by the VTC's policy engine; there are no defaults in the SDK. + +## Status + +The design is OpenVTC's `docs/design/vetting-process.md` (**DRAFT v3**, V0 targeted for **2026-10-05**); most of the protocol lives in the ToIP Trust Task specifications, and every wire type is generated from them. VTI landed the server half on 2026-09-11 (#1425–#1432, #1439): wire types and crypto, requirements in the manifest, statements counted in the join decision, the vetter role credential, a vetter registry with automatic grants, admin-UI panels and a `cnm` **PGP web-of-trust bootstrap** for migrating an existing strong set. OpenVTC shipped **V0** across #292–#343: the core and a **Vetting** page (Applications and the Vetting desk), cards read from a persona face, tickets and QR, the vetter directory and profile, an end-to-end ceremony over a real mediator, and join offers that list every way in. Out of scope for V0: zero-knowledge k-of-n proofs over hidden vetters, automated document verification, and Sybil-proof uniqueness across different join DIDs. + +See also: [[verifiable-trust-community]], [[personhood-credential]], [[dtg-credentials-overview]], [[openvtc]], [[verifiable-trust-infrastructure]] diff --git a/content/concepts/persona-credential.md b/content/concepts/persona-credential.md index adc8357..666c983 100644 --- a/content/concepts/persona-credential.md +++ b/content/concepts/persona-credential.md @@ -2,28 +2,40 @@ title: "Persona Credential (VPC)" type: concept tags: [credentials, dtg, persona, privacy, zkp] -date-updated: 2026-08-19 -sources: [dtg-credential-spec, dtg-credentials] +date-updated: 2026-09-18 +sources: [dtg-credential-spec, dtg-credentials, verifiable-trust-infrastructure] --- # Persona Credential (VPC) -A Verifiable Persona Credential links a [[did-types|P-DID (Persona DID)]] to an existing relationship. It lets you selectively reveal a persona identity — potentially a pseudonymous one — to a specific counterparty. +A Verifiable Persona Credential links a **persona** to an existing relationship. It lets you selectively reveal a persona identity — potentially a pseudonymous one — to a specific counterparty, so that they can prove they know you *in the context of that persona*. ## How It Works -The VPC is an [[credential-categories|Annotation Credential]] — it doesn't create new graph structure but attaches persona information to an existing relationship. The issuer is the P-DID itself, and the subject is the counterparty's DID. +The VPC is an [[credential-categories|Annotation Credential]] — it doesn't create new graph structure but attaches persona information to an existing relationship. The issuer is the persona's own identifier; the subject is the counterparty's identifier as used in the relationship. -This means: "I am revealing to you that this persona identity belongs to the same person you already have a relationship with." +This means: "I am revealing to you that this persona belongs to the same person you already have a relationship with." + +Since Working Draft 02 of the [[dtg-credential-spec|spec]] a persona is no longer "a P-DID" ([[did-types]] is retired). A persona is simply an identifier its holder has declared with a **`directed`** [[correlation-scope]] — "a private persona", known to the set of counterparties the holder chooses and no one else — or `public` for a public persona. The spec puts it well: *a persona is what a `directed` declaration is for.* Membership in a community does not require one; a member who declares `pairwise` toward the community has no persona there, while a member who wants to be recognised as the same person by other members, or across communities, is asserting a persona and declares the identifier `directed` accordingly. Asserting a persona is how a person controls intentional correlation *deliberately*, rather than acquiring it as a side effect of reusing an identifier. ## The "Banksy Maneuver" -The spec highlights a use case called the "Banksy Maneuver" — proving that you control a pseudonymous identity to a specific person, without revealing it to anyone else. For example, a developer known by their real name could prove to a trusted contact that they also control a well-known pseudonymous open-source identity, without publicly linking the two. +The spec highlights a use case called the "Banksy Maneuver" — proving that you control a pseudonymous identity to a specific person, without revealing it to anyone else. A developer known by their real name could prove to a trusted contact that they also control a well-known pseudonymous open-source identity, without publicly linking the two. -This is enabled by the DID separation in the [[did-types|DID taxonomy]]: your P-DID is distinct from your M-DID and R-DIDs, so linking them is a deliberate, selective act. +This works because the identifiers are separate by design: the persona identifier is distinct from the `pairwise` identifiers used in each relationship, so linking them is a deliberate, selective act. ## Relationship to Pairwise ZKPs -The Banksy Maneuver is one application of the broader **pairwise ZKP** construction the DTG spec defines on the [[relationship-credential|VRC]]. That construction lets a VRC holder selectively disclose chosen attributes — most usefully, the parties' P-DIDs — while hiding the underlying R-DIDs that constitute the private pairwise channel. The VPC is what makes the disclosed P-DID *meaningful* to a counterparty as a persona link, layered on top of the cryptographic primitive provided by the pairwise ZKP. See [[zero-knowledge-proofs]] for the broader framing. +The Banksy Maneuver is one application of the **pairwise ZKP** the spec defines on the [[relationship-credential|VRC]]: a VRC holder selectively discloses chosen attributes — most usefully, the parties' `directed` persona identifiers — while hiding the underlying `pairwise` ones that constitute the private channel. The VPC is what makes the disclosed persona *meaningful* to a counterparty as a persona link, layered on top of the cryptographic primitive. See [[zero-knowledge-proofs]]. + +Two WD02 privacy points bear on personas. Intentional correlation should occur *only* through a deliberate VPC or a deliberate `directed`/`public` declaration, never as a side effect of credential structure. And a persona's exposure is set by where it is used: the effective disclosure of any edge is the wider of its two halves, so a persona published under a `public` identifier by one party is correlated to that party even if the counterparty kept their own half `pairwise`. + +## On `main` + +The spec's `main` branch (heading for Working Draft 0.4.0) keeps the VPC as a concrete type even though it is statement-shaped — because its issuer *is* the persona identifier and so carries correlation-scope semantics that a generic [[statement-credential|VSC]] predicate could not. + +## Implementation Status + +VPC issuance shipped in the [[verifiable-trust-infrastructure|VTI]] (#1074). In [[openvtc|OpenVTC]] the persona identifier is the one a member joins communities with, derived at `m/1'/0'/` ([[bip32-key-derivation]]); relationships get their own `pairwise` identifiers by default (openvtc #254/#255). -See also: [[zero-knowledge-proofs]], [[did-types]], [[credential-categories]], [[dtg-credentials-overview]] +See also: [[zero-knowledge-proofs]], [[correlation-scope]], [[did-types]], [[credential-categories]], [[dtg-credentials-overview]] diff --git a/content/concepts/personhood-credential.md b/content/concepts/personhood-credential.md index 71b2666..a586cc3 100644 --- a/content/concepts/personhood-credential.md +++ b/content/concepts/personhood-credential.md @@ -2,8 +2,8 @@ title: "Personhood Credential (PHC)" type: concept tags: [credentials, dtg, personhood, sybil, governance] -date-updated: 2026-08-19 -sources: [dtg-credential-spec, dtg-credentials, openvtc] +date-updated: 2026-09-18 +sources: [dtg-credential-spec, dtg-credentials, openvtc, verifiable-trust-infrastructure] --- # Personhood Credential (PHC) @@ -15,9 +15,11 @@ A Personhood Credential is a [[membership-credential|Membership Credential (VMC) That's it. A PHC is structurally identical to any other VMC. There are no additional schema fields. What makes a VMC a PHC is the **governance and [[trust-registries|trust registry]]** of the issuing community, not the credential itself. +Since Working Draft 02 the spec is precise about *which* VMC: the PHC is the **community-issued grant**. A grant is a PHC whether or not the member has acknowledged it — the member may present an unacknowledged grant as evidence of their own membership (the presentation is itself their participation), and the acknowledgement gates only the community's ability to assert that membership to others. "Exactly one membership per person" is counted over grants and enforced by governance. See [[membership-credential]] for the pair. + ## The Key Design Choice -The DTG specification (v1.0 WD01 — and v0.3 before it) is emphatic about this: PHC status is "determined by governance and trust registries, not by credential structure." Issuers may optionally add `"PersonhoodCredential"` to the W3C type array as a **non-authoritative hint**, but this is not what makes it a PHC. The trust registry is the authoritative source. +The DTG specification (v1.0 WD02 — and WD01 and v0.3 before it) is emphatic about this: PHC status is "determined by governance and trust registries, not by credential structure." Issuers may optionally add `"PersonhoodCredential"` to the W3C type array as a **non-authoritative hint**, but this is not what makes it a PHC. The trust registry is the authoritative source. This means the same credential format works across communities with very different personhood verification standards. One community might require in-person verification; another might accept video calls; a third might use web-of-trust thresholds. The credential structure is the same — the governance differs. @@ -38,6 +40,8 @@ Without personhood enforcement, the [[decentralized-trust-graph|trust graph]] is PHCs are the defense at the community level: the community's governance ensures each membership belongs to a verified, unique human. +WD02 adds a third threat now that the graph has [[delegation-credential|delegation]]: **personhood laundering**. Because a delegate's acts are attributed to the delegator, a verifier that cannot distinguish the two may credit an AI agent with its principal's personhood — or count several agents of one person as several people. Verifiers must treat an act under a VDC as the delegate acting in the delegator's name, never as the delegator in person, and communities whose governance rests on personhood should state whether delegated acts are recognised at all. An agent acting under an attenuated [[authority-credential|VAC]] is credited with nothing a PHC attests about its principal. + ## How Personhood Is Verified The exact mechanism is a policy question for each community. Options include: @@ -47,6 +51,8 @@ The exact mechanism is a policy question for each community. Options include: - Existing identity document verification via an Identity Verification Provider (IDVP) - Web of trust thresholds (e.g., N existing members vouch for you) +In the ecosystem the current shape is a **personhood ceremony over Trust Tasks**: [[openvtc|OpenVTC]] #257 runs it with a spoken 8-character Crockford match code, the [[verifiable-trust-infrastructure|VTI]] carries personhood over messaging with in-person vetting as evidence (#1085/#1086), and OpenVTC's [[peer-identity-vetting]] design names community vetters by a revocable role VEC. On the spec's `main` branch such evidence is exactly what a [[statement-credential|statement credential]] is for — an `isHuman`-style statement is weighed by the community when deciding whether a membership qualifies as a PHC; it never establishes personhood by itself. + ## Lending Personhood to Relationship Proofs A PHC's value extends beyond the membership edge itself: it can be carried forward into ZKP proofs of [[relationship-credential|VRCs]]. When two members of a PHC-issuing community have a VRC between them, the holder can construct a community-anchored ZKP showing: @@ -55,6 +61,6 @@ A PHC's value extends beyond the membership edge itself: it can be carried forwa 2. Possession of a VMC from the community 3. That the VRC counterparty also holds a VMC from the *same* community -Because that community's VMCs are PHCs, the resulting proof carries personhood assurance for both parties without revealing their underlying DIDs. This is one proof construction available to relationships inside a shared PHC-issuing community — not a universal requirement for VRCs, which can also exist directly between individuals outside any community context. +Because that community's VMCs are PHCs, the resulting proof carries personhood assurance for both parties without revealing their underlying identifiers. This is one proof construction available to relationships inside a shared PHC-issuing community — not a universal requirement for VRCs, which can also exist directly between individuals outside any community context. WD02 notes that step 3 currently proves the community *attested* the counterparty's membership, not that the counterparty acknowledged it, and that under `pairwise` [[correlation-scope|scopes]] the proof must also establish common control of two identifiers — see [[zero-knowledge-proofs]]. -See also: [[membership-credential]], [[trust-registries]], [[decentralized-trust-graph]], [[dtg-credentials-overview]] +See also: [[membership-credential]], [[trust-registries]], [[decentralized-trust-graph]], [[dtg-credentials-overview]], [[delegation-credential]], [[peer-identity-vetting]] diff --git a/content/concepts/post-quantum-cryptography.md b/content/concepts/post-quantum-cryptography.md new file mode 100644 index 0000000..4383b76 --- /dev/null +++ b/content/concepts/post-quantum-cryptography.md @@ -0,0 +1,50 @@ +--- +title: "Post-Quantum Cryptography in the Stack" +type: concept +tags: [cryptography, post-quantum, ml-dsa, ml-kem, keys, data-integrity, tsp, security] +date-updated: 2026-09-18 +sources: [affinidi-tdk, verifiable-trust-infrastructure, verifiable-trust-agent, didwebvh-rs] +--- + +# Post-Quantum Cryptography in the Stack + +## Why a Trust Graph Cares Now + +Every signature and every key agreement in this ecosystem today rests on elliptic curves — Ed25519 for signing, X25519 for encryption — and a sufficiently large quantum computer would break both. Nobody has one. The reason to act anyway is **harvest now, decrypt later**: traffic recorded today can be opened whenever the machine arrives, and a signature forgeable later undermines any record that relies on it. A [[decentralized-trust-graph|trust graph]] is exactly that kind of long-lived record. A [[membership-credential|membership]] or [[relationship-credential|relationship]] credential may be relied on for years; a [[did-webvh|did:webvh]] log is signed once and cannot be re-signed; a personhood decision recorded in 2026 should still be checkable in 2040. So the ecosystem's posture is *hybrid* — classical and post-quantum proofs side by side — because the migration will take years and both kinds of verifier will coexist throughout. + +The algorithms are NIST's: **ML-DSA** (FIPS 204, the lattice signature scheme formerly Dilithium) in the 44 and 65 parameter sets, and **ML-KEM-768** (FIPS 203, formerly Kyber) for key encapsulation, always paired with X25519 as the hybrid `MLKEM768-X25519`. + +## What Exists, Layer by Layer + +**The TDK, since April 2026.** `affinidi-tdk-rs` v0.5.4 (2026-04-18) added post-quantum support to the crypto and Data Integrity crates behind a `post-quantum` umbrella feature (per-algorithm `ml-dsa` and `slh-dsa` sub-flags, all off by default): `KeyType::{MlDsa44, MlDsa65, MlDsa87, SlhDsaSha2_128s}` in `affinidi-crypto`, `Secret::generate_ml_dsa_*` in the secrets resolver, official multicodec code points, and the W3C `di-quantum-safe` cryptosuites **`mldsa44-jcs-2024`** and **`mldsa44-rdfc-2024`** (plus `slhdsa128-*`). Only ML-DSA-44 gets a Data Integrity suite because that is the only parameter set the W3C draft fully specifies; ML-DSA-65/87 have primitives but no suite. The scope was Data Integrity only — DIDComm, mdoc/COSE and JWT stayed classical, as they still do. The `sign_multi_hybrid` example already showed the migration pattern: one credential, an Ed25519 proof *and* an ML-DSA proof, so each kind of verifier accepts the one it understands. + +**TSP Rev 3, September 2026.** The [[trust-spanning-protocol|TSP]] specification's Rev 3 defines a PQ profile (§8): hybrid `MLKEM768-X25519` HPKE and ML-DSA-65 signatures. `affinidi-tsp` 0.2.0 implements it behind a `pq` feature and opens the spec's `direct-hpke-base-pq` vector end to end — but only after four defects in the published appendix (truncated values, a 32-byte ML-KEM "key" that turned out to be a seed, a naming clash with X-Wing) were fixed upstream — otherwise "a hybrid KEM would have shipped validated by nothing but agreement with themselves". The cryptography is verified; the **VID model is not PQ-aware** (`ResolvedVid` keys are 32 bytes; an ML-DSA-65 verifying key is 1952), so nothing above the crate can use the path yet. The CESR code point `1AAQ` is provisional. + +**VTI keys and templates, 2026-09-16 → 09-18.** The VTA's `KeyType` gained **`MlDsa44` and `MlDsa65`** (#1502) — two sets because two specs demand different ones: W3C suites exist for ML-DSA-44, TSP Rev 3 §8.1 mandates ML-DSA-65. Both **derive from the [[bip32-key-derivation|BIP-32]] chain** (#1505) with per-parameter-set domain separation, so the seed never equals the Ed25519 key at the same path, and a key record now **carries the algorithm it was minted with** (#1532) instead of a literal at every save site; `pnm keys create --type ml-dsa-*` shows which axis a key protects (#1535). DID templates declare which algorithms each key slot uses (#1530), the VTA accepts the **did-templates 3.0** task family alongside 2.0 (#1538), clients carry a PQ template (#1542), and a template can name a **third key slot** beyond the classical signing/key-agreement pair (#1554 — following the loader's earlier error message would have published a `PLACEHOLDER-NEVER-SUBSTITUTED` literal into a write-once did:webvh log). + +**Hybrid credentials.** VTC verification reads a document carrying more than one proof (#1548, #1550), and the RC commit (#1553) has **the VTC sign with every key it holds** — one proof per key, each under its own cryptosuite. A single-key VTC emits byte-identical output, so nothing downstream changes until a second key exists; #1557 provisions a VTC from a v2 template that issues hybrid Ed25519 + ML-DSA credentials. See [[verifiable-credentials]]. + +**did:webvh.** [[didwebvh-rs]] exposes ML-DSA-44/65/87 and SLH-DSA suites for log entries and witness proofs behind an `experimental-pqc` feature, explicitly off-spec: did:webvh 1.0 does not standardise them. + +## Built, Tested, Unreachable + +The most instructive September finding was not cryptographic. Three times, PQ code existed and could not be reached: + +- `affinidi-data-integrity` was pinned by VTI **without its `ml-dsa` feature**, so the signer fell back to `EddsaJcs2022` and failed with "key type MlDsa44 is not compatible" — every capability built above it led to a key that could not sign: "built, tested, and unreachable" (#1553). VTI now pins the feature on. +- ML-DSA secrets **could not be persisted** (TDK #797): the generator wrote an empty `privateKeyMultibase`, so a saved store lost the key on restart. Fixed via the `-priv-seed` codecs; SLH-DSA stays memory-only because FIPS 205 has no registered private-key multicodec. +- The Data Integrity crate's **ML-DSA tests had never compiled** (TDK #821): `default = []`, so ten unit tests never ran and one passed for the wrong reason. + +## Status: On by Default, or Behind a Flag + +| Layer | Status | +|---|---| +| VTA key types ML-DSA-44/65, BIP-32 derived, algorithm-carrying records | **On** (vta-keys 0.6, vta-sdk 0.42+) | +| did-templates 3.0, per-slot algorithms, third slot | **On** | +| VTC hybrid multi-proof issuance and verification | **On**; hybrid output only once a second key is held | +| TDK Data Integrity ML-DSA-44 suites | Behind `ml-dsa` in the TDK; **on** in VTI's pin | +| TSP Rev 3 `MLKEM768-X25519` + ML-DSA-65 | Behind `pq`; VID model not PQ-aware, so unused above the crate | +| didwebvh-rs PQ suites | Behind `experimental-pqc`, off-spec | +| DIDComm, mdoc/COSE, JWT, TEE attestation | Classical only | +| Internal (non-derived) PQ keys | Refused pending a decision on holding an unrecoverable PQ key | + +See also: [[trust-spanning-protocol]], [[bip32-key-derivation]], [[verifiable-credentials]], [[verifiable-trust-agent]], [[affinidi-tdk]] diff --git a/content/concepts/relationship-credential.md b/content/concepts/relationship-credential.md index 18c2256..3465936 100644 --- a/content/concepts/relationship-credential.md +++ b/content/concepts/relationship-credential.md @@ -2,8 +2,8 @@ title: "Relationship Credential (VRC)" type: concept tags: [credentials, dtg, relationships, trust, edge, zkp] -date-updated: 2026-08-19 -sources: [dtg-credential-spec, dtg-credentials, openvtc] +date-updated: 2026-09-18 +sources: [dtg-credential-spec, dtg-credentials, openvtc, verifiable-trust-infrastructure] --- # Relationship Credential (VRC) @@ -12,56 +12,56 @@ A Verifiable Relationship Credential attests to a trust relationship between two ## Bidirectionality: Two VRCs = One Edge -The spec is explicit: **"Two VRCs (one each direction) form a complete DTG edge."** A relationship is verified through a bi-directional pair of VRCs. When Alice and Bob establish a trust relationship, each issues one VRC to the other. Alice's VRC says "I trust Bob"; Bob's VRC says "I trust Alice." Together, these two credentials form a complete, bilateral trust edge in the graph — the relationship doesn't exist in the DTG until both halves are issued. +The spec is explicit: **"Two VRCs (one each direction) form a complete DTG edge."** When Alice and Bob establish a trust relationship, each issues one VRC to the other. Alice's VRC says "I trust Bob"; Bob's says "I trust Alice." Together they form a complete, bilateral edge — the relationship doesn't exist in the DTG until both halves are issued. Each half is signed by its own issuer and evaluated independently; nothing requires a single credential to carry both signatures (Working Draft 02 struck an old glossary condition that said otherwise, since no credential could satisfy it). ## The Relationship Protocol -Establishing a relationship and exchanging VRCs follows a multi-step protocol over [[didcomm|DIDComm]]: +Establishing a relationship and exchanging VRCs follows a multi-step protocol over [[didcomm|DIDComm]] or [[trust-spanning-protocol|TSP]]: 1. **Request** — Alice sends a relationship request to Bob 2. **Accept** — Bob accepts the request -3. **Finalize** — both parties confirm, creating a private channel with a dedicated [[did-types|R-DID]] (Relationship DID) +3. **Finalize** — both parties confirm, creating a private channel under dedicated per-relationship identifiers 4. **VRC Request** — either party requests a formal credential 5. **VRC Issuance** — the other party issues a signed VRC -The spec requires that **each entity MUST generate a new, unique R-DID for every single entity they connect with**, even within the same community. This is critical for privacy — your [[did-types|M-DID]] isn't exposed in individual relationships. - For witnessed exchanges, see the [[witnessed-vrc-exchange|Witnessed VRC Exchange Protocol]]. +## Identifiers and Privacy + +WD02 replaced the old "each entity MUST generate a new R-DID per relationship" rule with the same idea stated through [[correlation-scope]]: a **`pairwise`** identifier is RECOMMENDED for a VRC, an identifier declared `pairwise` MUST NOT be used with more than one counterparty, and a verifier that sees it with a second counterparty MUST treat the declaration as false. Implementations SHOULD default to minting a distinct identifier per relationship. A wider declaration is permitted — `directed`, for a relationship the holder intends to be correlated with others they choose, including relationships inside a shared [[verifiable-trust-community|VTC]] — but it is a disclosure the holder makes deliberately. Each peer chooses its own identifier and scope; the two halves need not agree, and an edge's effective disclosure is the *wider* of the two. + +An identifier minted for one relationship and declared `pairwise` is, from its controller's point of view, a globally unique name for that edge — so semantic statements, metadata or private context about the relationship can be anchored to the controller's own identifier without resolving the counterparty's (*Unilateral Relationship Identification*). + ## What a VRC Contains -- **Issuer** — the [[did-types|R-DID]] or M-DID of the person attesting -- **Subject** — the R-DID or M-DID of the counterparty +- **Issuer** — the source party's identifier (`pairwise` RECOMMENDED) +- **Subject** — the target party's identifier *as used in this relationship* - **Type** — `["VerifiableCredential", "DTGCredential", "RelationshipCredential"]` -- **Validity period** — when the attestation is valid -- **Proof** — EdDSA JCS 2022 Data Integrity signature +- **Validity period**; optional **`taskContext`** ([[trust-task-context-binding]]) +- **Proof** — Data Integrity signature -## ZKP Presentation +## When Is a VRC an Edge? (Edge Verifiability, WD02) -VRCs can stand alone — two individuals can issue VRCs to each other without either being a member of any [[verifiable-trust-community|VTC]], and the resulting edge is a valid trust attestation. The cryptographic signatures speak for themselves; the meaning of the attestation is whatever real-world context the parties bring to it. +A VRC whose proof verifies establishes that its issuer made the statement. Whether a verifier *also* treats it as an edge of a particular graph is a separate determination, and WD02 (PR #26) defines it **relative to the verifier**: a VRC is verifiable as a DTG edge *by a given verifier* when its proof verifies and the verifier can establish that the issuer's membership in a VTC **in the anchor set that verifier accepts** is complete. A [[verifiable-trust-network|VTN]] is the common anchor set, but none need exist; the same VRC may be an edge to one verifier and not to another, and neither is wrong. The membership condition may be met **by disclosure** (the VMC pair) or **by proof** (the community-anchored ZKP), and the spec insists the two are equivalent in principle — a `pairwise` did:peer VRC with a valid proof is an edge on the same terms as one issued from the VMC's own identifier. This matters because the pairwise form is the one the spec recommends, and a definition that admitted only disclosure would have excluded it. -The spec defines two ZKP constructions for VRCs and recommends ZKP presentation by default whenever privacy matters. See [[zero-knowledge-proofs]] for the full discussion. +## ZKP Presentation -### Pairwise ZKP (per VRC, no shared community required) +VRCs can stand alone — two individuals can issue VRCs to each other without either being a member of any VTC, and the resulting edge is a valid trust attestation on the strength of the signatures and whatever real-world context the parties bring. The spec defines two ZKP constructions and recommends ZKP presentation by default. See [[zero-knowledge-proofs]]. -The holder of a VRC MAY construct a zero-knowledge proof that demonstrates possession of a valid VRC and selectively discloses chosen attributes, subject DIDs, or predicates over them. The canonical application is to disclose the parties' [[did-types|P-DIDs]] while hiding the underlying [[did-types|R-DIDs]] — a public, verifiable claim that two known personas have a relationship, without exposing the private pairwise channel between them or enabling correlation across the holder's other presentations. +### Pairwise ZKP (per VRC, no shared community required) -This construction is available to **any two parties who hold a VRC between them**, regardless of shared community membership. It does not by itself confer community-level assurance like personhood; whatever assurance it carries derives from out-of-band context, the public reputation of any disclosed persona DIDs, and the cryptographic integrity of the VRC. +The holder MAY prove possession of a valid VRC while selectively disclosing chosen attributes, identifiers, or predicates. The canonical application is to disclose the parties' `directed` persona identifiers while hiding the underlying `pairwise` ones — a public, verifiable claim that two known [[persona-credential|personas]] have a relationship, without exposing the private channel between them. Available to any two VRC holders; confers no community-level assurance by itself. ### Community-Anchored ZKP (when both parties hold VMCs from the same community) -When both parties to a VRC hold [[membership-credential|VMCs]] from the same community, the holder MAY additionally construct a **community-anchored ZKP** of the relationship — anchored under the VMC. The proof demonstrates: - -1. Possession of the VRC -2. Possession of the underlying [[membership-credential|VMC]] (proving membership) -3. The VRC issuer holds a VMC from the *same* [[did-types|C-DID]] - -This proof anchors the relationship within a community's governance context — when the community's VMCs are also [[personhood-credential|PHCs]], that personhood assurance carries forward — without revealing the underlying DIDs. It is **one proof construction available to relationships within a shared community**, not a universal precondition for issuing or holding a VRC. +When both parties hold [[membership-credential|VMCs]] from the same community, the holder MAY prove (1) possession of the VRC, (2) possession of their own community-issued VMC, and (3) that the VRC issuer holds a VMC from the *same* community identifier. WD02 adds two honest caveats: statement 3 currently establishes only that the community *attested* the counterparty's membership, not that the counterparty acknowledged it; and where a party used a `pairwise` identifier toward the community and another toward the counterparty, the proof must additionally establish **common control** of the two — a primitive the ZKP task force has not yet delivered (spec issue #9). When the community's VMCs are [[personhood-credential|PHCs]], that assurance carries forward. ## Trust Graph Significance -VRCs are the edges that make the trust graph traversable. When evaluating trust in someone you don't know directly, you look for paths through the graph — chains of VRCs connecting you to them through mutual connections. Multiple independent paths increase confidence. +VRCs are the edges that make the trust graph traversable. When evaluating trust in someone you don't know directly, you look for paths through the graph — chains of VRCs connecting you to them through mutual connections. Multiple independent paths increase confidence. VRCs can be strengthened by [[witness-credential|Witness Credentials]] and complemented by [[endorsement-credential|Endorsement]] and [[persona-credential|Persona Credentials]]. A [[delegation-credential|VDC]] neither requires nor implies a VRC between delegator and delegate — relationship evidence and delegation evidence are different things. + +## Implementation Status -VRCs can be strengthened by [[witness-credential|Witness Credentials]] and complemented by [[endorsement-credential|Endorsement Credentials]], [[persona-credential|Persona Credentials]], and other [[credential-categories|Annotation Credentials]]. +In [[openvtc|OpenVTC]] since Dogwood (openvtc #254/#255, requiring VTI #1061) a pairwise relationship identifier is the default and the VRC is issued under it — in WD02 terms a `pairwise`-scope identifier per relationship. The [[verifiable-trust-infrastructure|VTI]] distinguishes half-edges (one VRC) from complete edges (both) on the graph (#1073). -See also: [[zero-knowledge-proofs]], [[dtg-credentials-overview]], [[credential-categories]], [[witnessed-vrc-exchange]], [[did-types]] +See also: [[zero-knowledge-proofs]], [[dtg-credentials-overview]], [[credential-categories]], [[witnessed-vrc-exchange]], [[correlation-scope]], [[verifiable-trust-network]] diff --git a/content/concepts/statement-credential.md b/content/concepts/statement-credential.md new file mode 100644 index 0000000..8bdd3be --- /dev/null +++ b/content/concepts/statement-credential.md @@ -0,0 +1,56 @@ +--- +title: "Statement Credential (VSC)" +type: concept +tags: [credentials, dtg, statement, predicate, annotation, spec, draft] +date-updated: 2026-09-18 +sources: [dtg-credential-spec] +--- + +# Statement Credential (VSC) + +> **Status: on the spec's `main` branch only.** The VSC landed in `dtgwg-cred-spec` on 2026-09-15 (commit `5a660ea`, refs issues #45, #48, #52), *after* the `WD02` tag of 2026-09-07, and is heading for Working Draft 0.4.0. It is **not in WD02**, and the [[dtg-credentials]] crate — which tracks WD02 — does not implement it: the crate still has concrete `EndorsementCredential` and `WitnessCredential` types. Read this page as where the spec is going, not 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 [[relationship-credential|VRC]], [[membership-credential|VMC]], [[delegation-credential|VDC]], [[authority-credential|VAC]] and [[invitation-credential|VIC]]. The [[persona-credential|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 become the first two profiles, **`dtg:endorses`** (the [[endorsement-credential|VEC]]) and **`dtg:witnessed`** (the [[witness-credential|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 goes 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 [[personhood-credential|PHC]], a role statement when deciding to issue a VAC. The statement never establishes the membership, personhood or authority itself. + +## Shape + +- `type` includes `StatementCredential` and no other concrete subtype +- `issuer` — the party making the statement; a profile MAY set a minimum correlation scope +- `taskContext` — OPTIONAL unless the profile requires 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), or `value` (a payload whose schema the profile states) +- further members as the profile defines; verifiers MUST ignore members a profile does not define + +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. + +## 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 unrecognised 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-normalised IRIs; predicates resolve to their definitions at *configuration* time, never at verification. Vocabularies are additive — terms are deprecated, never changed or removed — and the namespace carries no version segment. + +The rationale is explicit: a well-formed statement under an unrecognised 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 two core profiles + +- **`dtg:endorses` (VEC)** — evidence; `object.value` structured by the community's endorsement vocabulary; `taskContext` OPTIONAL. 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. +- **`dtg:witnessed` (VWC)** — evidence weighed by the community whose witnessing policy applies; `object.digestMultibase` names the witnessed edge credential; **`taskContext` REQUIRED**; issuer `directed` at minimum; optional `witnessContext`. Direction binding is now **unconditional**: `credentialSubject.id` MUST be the *issuer* of the credential the object names. A pass establishes that the witness observed the subject issue those claims in that exchange — not that the credential is still valid, that its claims are true, or that the exchange completed. + +`dtg:` abbreviates `https://firstperson.network/credentials/dtg/v1#` in the document; on the wire the predicate is the absolute IRI. The namespace is a placeholder (issue #48); the final form will be unversioned. + +## Community predicates and the registry + +A VTC or VTN defines its own predicate by publishing, under a namespace it controls, the nine things a profile must state — IRI and labelled meaning; evidence vs assertion of status; permitted object kinds; subject–object relationships; extra members; whether `taskContext` 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 behaviour, since which vocabularies count is decided by the governance a verifier relies on, not by an issuer. Predicates beyond the core profiles will live in a planned **DTG Predicate Vocabulary**, a repo-driven registry on the model of the ToIP glossary (issue #52) that curates a default set without gating who may make a statement. + +## What it means for older wiki pages + +Where [[endorsement-credential]], [[witness-credential]] and [[witnessed-vrc-exchange]] describe `EndorsementCredential` / `WitnessCredential` types with `credentialSubject.endorsement` or `credentialSubject.digestMultibase`, that is the WD02 shape implementations use today. On `main` the same content moves under `object.value` and `object.digestMultibase`, beneath a `predicate`. The [[openvtc|OpenVTC]] `CommunityRole` VEC used in [[peer-identity-vetting]] would become a `dtg:endorses` or community-defined statement. + +See also: [[endorsement-credential]], [[witness-credential]], [[credential-categories]], [[dtg-credentials-overview]], [[dtg-credential-spec]] diff --git a/content/concepts/trust-spanning-protocol.md b/content/concepts/trust-spanning-protocol.md index f6d3054..817338d 100644 --- a/content/concepts/trust-spanning-protocol.md +++ b/content/concepts/trust-spanning-protocol.md @@ -1,34 +1,82 @@ --- title: "Trust Spanning Protocol (TSP)" type: concept -tags: [tsp, messaging, trust-over-ip, encryption, transport] -date-updated: 2026-08-19 -sources: [affinidi-tdk, verifiable-trust-infrastructure, affinidi-webvh-service, openvtc, vta-browser-plugin, vti-setup] +tags: [tsp, messaging, trust-over-ip, encryption, transport, relationships, post-quantum, conformance, eucalyptus] +date-updated: 2026-09-18 +sources: [affinidi-tdk, verifiable-trust-infrastructure, verifiable-trust-agent, affinidi-webvh-service, openvtc, vta-browser-plugin, vti-didcomm-js, vti-setup] --- # Trust Spanning Protocol (TSP) -The Trust Spanning Protocol is a messaging protocol specified by the Trust Over IP Foundation (TSP Specification Rev 2, November 2025 Experimental Implementer's Draft). It's a leaner alternative to [[didcomm|DIDComm]] — and as of late June 2026, it is the ecosystem's **preferred transport**: official guidance across the [[verifiable-trust-infrastructure|VTI]] flipped to **TSP > DIDComm > REST**. +The Trust Spanning Protocol is the Trust Over IP Foundation's messaging protocol for parties identified by verifiable identifiers — in this ecosystem, [[decentralized-identifiers|DIDs]]. It is the **preferred transport** of the whole stack (the official order is TSP > [[didcomm|DIDComm]] > REST), and as of the `VTI-Eucalyptus-RC-0` cycle ([[coordinated-releases]], September 2026) every implementation in the ecosystem speaks **TSP specification Rev 3** — version marker `YTSP-AAC`, the merged specification at trustoverip/tswg-tsp-specification commit `f5b8668`. -## Key Characteristics +The one-sentence pitch: TSP gives two DIDs an end-to-end authenticated, encrypted channel whose *intermediaries* — the mediators that relay it — can be kept from learning who is talking to whom, at a message size that grows additively per hop rather than multiplying the way nested DIDComm does. That routing privacy is why the ecosystem adopted it, and (as the September 2026 decision below records) why it is keeping it. -- **HPKE-Auth encryption** — Hybrid Public Key Encryption (RFC 9180) with authentication -- **CESR binary encoding** — Composable Event Streaming Representation, a compact binary format -- **Simpler than DIDComm** — fewer layers, less complexity, designed for high-performance scenarios -- **Direct, Routed, and Nested message modes** — including relay through mediators and cross-mediator federation +## What Rev 3 Is -## How the Ecosystem Adopted It +Rev 3 is not an increment on Rev 2; it is a different wire. The crypto mode, the version byte, the long count-code prefix, the ciphertext layout and every payload type code changed at once, so "there is no frame the two revisions can both classify". The ecosystem therefore cut over as a **flag day with no Rev 2 compatibility on the Rust side** — a Rev 2 frame arriving at a Rev 3 peer is reported as `TspError::RevisionMismatch` rather than misread. The pieces that matter to a reader of this wiki: -TSP's promotion happened in a coordinated burst across three repos in June–July 2026: +- **HPKE-Base, not HPKE-Auth.** Rev 2 authenticated the sender inside the key encapsulation. Rev 3 uses RFC 9180's base mode and carries sender authenticity in the **ESSR signature alone** (Encrypt-then-Sign with the sender's VID inside the confidential payload, bound by AEAD associated data of `TSP_Version ‖ VID_sndr ‖ VID_rcvr`). This is the change with the largest architectural consequence: nothing in the middle can re-sign a message and still be honest about who sent it — which is exactly why the DIDComm↔TSP bridge had to go (below). +- **CESR encoding**, tightened: `--X#####` long count codes, one `-E` frame whose count covers the ciphertext (so a message past ~12 KB is *long-framed* and starts `--E`), a 128-bit nonce, self-addressing `TSP_Digest` values (§7.2.1) that a receiver can actually recompute, and non-canonical padding rejected outright. New payload codes `XSCS / XHOP / XRFI / XRFA / XRFD / XCTL / XPAD`. +- **Relationships are enforced (§7.2.2).** An endpoint *drops* — silently, by design — any application message from a VID it holds no relationship with. Relationships are formed by an invite/accept handshake (`XRFI` / `XRFA`), with an invite-race tiebreak by digest, three-case cancellation, `Reply_Path`, `Referral_Field`, fillable padding and upper-layer control messages. Under Rev 2 the ecosystem recorded relationships; under Rev 3 it must *form them before any Trust Task*. +- **Direct, Routed and Nested modes** survive, with `MAX_HOPS` raised from 16 to 64 and §5.3.3's rule that an intermediary must never persist the endpoint-to-endpoint VIDs of a nested message. +- **Post-quantum** is now in the specification (§8): a hybrid `MLKEM768-X25519` KEM and ML-DSA-65 signatures, with a published test vector. See [[post-quantum-cryptography]] for what is wired and what is not. -- **[[affinidi-tdk|Affinidi TDK]]** did the heavy lifting (~60 commits): the `affinidi-tsp` crate graduated from experimental to **supported**, was declared fully interoperable with the ToIP reference `tsp_sdk` (verified by a round-trip interop harness), and the mediator became dual-protocol — it sniffs DIDComm vs TSP frames on the *same* endpoint and websocket, bridges between the two, and federates TSP across mediators. Clients select TSP automatically when a peer supports it, with DIDComm fallback. -- **[[verifiable-trust-infrastructure|VTI]]** initially *deferred* TSP (2026-06-22 decision record) because the mediator couldn't route it — then reversed that decision three days later when the TDK work landed, and shipped TSP as a first-class managed service on the VTA (enable/disable/rollback via `pnm services`, DID templates advertising a `TSPTransport` service, a TSP health probe). -- **[[affinidi-webvh-service|did-hosting-service]]** added TSP as a third transport binding alongside HTTPS and DIDComm — possible in one PR because every wire operation there is a transport-agnostic **Trust Task** document ("everything is a trust task"). +The version field is worth a footnote because it is a live interop question: the merged spec reads the three characters after `YTSP-` as MAJOR (one character) . MINOR (two), so Rev 3 is `AAC` where Rev 2 was `AAB`; pre-merge drafts printed `ABA` under a MAJOR.MINOR.PATCH reading. Only MAJOR gates processing in every implementation, so the disagreement (raised upstream on spec PR #63) does not affect whether messages open. -Design decisions worth knowing: DIDs double as TSP VIDs **reusing existing Ed25519/X25519 keys** (no new key material); capability discovery is DID-document-driven — peers advertise a service of type `TSPTransport`, and senders prefer it over `DIDCommMessaging` when present; TSP rides the same per-DID mediator websocket as DIDComm, so no second socket. +## The Flag Day Across the Stack — September 2026 -## Relationship to DIDComm +Because nothing a Rev 2 peer packs can be unpacked by a Rev 3 one, every repo had to move in the same window: -TSP and DIDComm serve similar purposes (secure, DID-based messaging) but make different trade-offs. DIDComm is more established with broader tooling; TSP is leaner and tracks the ToIP standards direction. The current posture is **TSP-preferred, DIDComm as the interop layer** — and by August 2026 that is implemented, not aspirational: the VTA exposes TSP as a *selectable* transport (`Auto` = TSP > DIDComm > REST), can run **TSP-only with no DIDComm at all**, offers TSP in its setup wizard and advertises it on minted DIDs; communities choose and publish their transports at setup, and the trust registry is reachable by DID over TSP; did-hosting-service decoupled TSP from DIDComm with a three-way transport selection and pushes outbound sync over TSP; [[openvtc]] runs the join ceremony over TSP when the community offers it (choosing TSP only when its own mediator can carry it); and the browser wallet ships a pure-TypeScript `vti-tsp-js`. The [[vti-setup]] Cypress walkthroughs default every component to TSP + DIDComm. What remains DIDComm-only: the did:webvh witness, and interop with the wider Aries/Credo world (for which the mediator now also speaks DIDComm v1). +- **[[affinidi-tdk|Affinidi TDK]] — `affinidi-tsp` 0.2.0 (#796, 2026-09-15).** The hard wire break: HPKE-Auth → HPKE-Base, all the encoding changes, `RelationshipPolicy::Gated` as the default, key-state re-resolution on failure (§7.4.2), the PQ path behind a `pq` feature verified against the spec's own vector, and the specification's ten Appendix A vectors run as a test suite (checked against the ToIP reference `tsp_sdk` 0.10.0's `rev3.json` — all pass, and five defects in the published appendix were found and reported on the way). Two latent bugs surfaced: long-form counts decoded to the wrong length, and long-framed messages were not recognised as TSP by any ingress classifier. Up the stack the messaging SDK (0.25 → 0.26.7) enforces relationship state on every unpack and grew a durable `PersistentRelationshipStore` plus a recovery runtime; the mediator (0.25.0) **removed the DIDComm↔TSP bridge** and collapsed five ACL-revealing refusal codes into one uniform `delivery.refused`. The libsodium sealed box (§8.3) is implemented for *reading* legacy peers only. +- **[[verifiable-trust-infrastructure|VTI]] — #1512 (2026-09-16).** The workspace's own inventory (`tsp-rev3-migration.md`) concluded that "the one thing that breaks silently" is §7.2.2: an unmigrated VTA that does not answer invites looks, from every client, like a transport that accepts connections and never replies. So the VTA and VTC now carry Trust Tasks inside the published **`tsp/0.1` binding envelope** (#1478, #1488), form a relationship before any send or health ping (#1527, #1537, #1540), answer invites instead of ignoring them (#1525), **persist relationships across restarts** (#1531), and re-form them after a reply timeout on the client (#1544), in the VTC registry client (#1546) and for the VTA's own server-initiated sends via a single-flight `RecoveryCoordinator` (#1549). Alongside it: **one outbound transport seam** chosen from what a peer advertises, replacing four hand-rolled carriages (#1474, with the webvh selector folded onto it in #1483), reply correlation so TSP can *initiate* (#1482), and the rule that there is one TSP transport and it must carry a mediator (#1507). +- **[[affinidi-webvh-service|did-hosting-service]] — #194.** An *answering* arm (it had only ever sent) plus persisted relationships, so a VTA's did:webvh host stays reachable over TSP after either side restarts. +- **[[openvtc|OpenVTC]] — #333 (= Eucalyptus-RC-0).** Forms the relationship (`form_relationship_routed`, idempotent) before any VTC Trust Task, recognises inbound `RelationshipControl`, persists relationships in `ProtectedConfig`, and gained a **TSP Relationships** pane (None / Pending / Invite received / Bidirectional per community) and an `openvtc health` finding that names the "no relationship → dropped" case — the explanation for a join to a healthy VTC that never gets a reply. +- **[[vta-browser-plugin|Browser wallet]] — `@openvtc/vti-tsp-js` 0.3.0 (#253).** Pure-TypeScript TSP that *packs* Rev 3 only and *reads* Rev 3 and Rev 2 (a frozen decode-only codec, to be deleted once the last Rev 2 peer is gone), with the XRFI/XRFA handshake and §7.2/7.3 state machine. [[vti-didcomm-js]] 0.10.0 fixed the demultiplexer that had been sending Rev 3 long frames (`--E`) to the DIDComm unpacker — a poison frame redelivered forever while the TSP consumer never heard; the mediator's `affinidi_tsp::is_tsp` was fixed in step. The mobile core (`vta-mobile-core`) moved to Rev 3 sealing in the same window. -See also: [[didcomm]], [[affinidi-tdk]], [[verifiable-trust-infrastructure]] +## Relationships: Admission, and Why the Ecosystem Paid for Them + +The §7.2.2 relationship cost a visible run of fixes, and VTI wrote a decision note (`tsp-vs-didcomm-trust-task-surface.md`) to ask honestly whether it was worth it. Its framing is the clearest explanation of what a TSP relationship *is*: it is **not authentication** (the envelope proves the sender, as DIDComm's does, and a Trust Task carries its own signed proof) and **not authorization** (that is the ACL). It is **admission** — "will I accept traffic from this VID at all" — and it is silent by design, because an unrelated VID must not be able to learn whether a target VID is live. + +The honest cost ledger: a handshake before the first Trust Task; symmetric silent drops that show up only as timeouts (from the client's side an unresponsive VTA and a VTA that forgot the relationship are indistinguishable); and the recovery machinery that implies. `tsp-relationship-recovery.md` designs that machinery as D1–D9: a durable store (D1), an idempotent re-establish transition (D2 — a naive re-invite deadlocks the state machine in both directions), recovery-aware send readiness (D3), bounded single-flight jittered recovery (D4), 7-day idle eviction (D5), a coordinator with startup enumeration (D6, D9), an inbound-invite rate limiter (D7) and drop metrics (D8). D1–D6 and D8 landed by 2026-09-18; D7 and D9 hardening remain. + +The decision, recorded as **decided**: multi-hop TSP — Trust Tasks routed where intermediaries never learn the final recipient — is a *requirement*, so the relationship layer is "the price of a benefit we will use" and the recovery work is "justified investment, not speculative rent". The nuance the note is careful about: on today's reference topology (PNM and VTA sharing one mediator) the privacy benefit is marginal, because the delivery mediator must know the recipient anyway; the benefit materialises only with intermediaries *before* the recipient's mediator. The first exercise of that benefit landed the day after the RC: **cross-mediator sends are nested** so the sender's own mediator never learns the recipient (#1559). + +## Cross-Implementation Conformance + +TSP now has five implementations in this ecosystem's orbit, and a new repository — **github.com/OpenVTC/tsp-conformance** (created 2026-09-16) — runs them against each other: one runner, a driver per implementation, and CI. The implementations are `affinidi-tsp` 0.2.0 (Rust), the ToIP reference `tsp_sdk` 0.11.0, `@openvtc/vti-tsp-js` 0.3.0, and two new ports, **`affinidi-tsp-go` 0.1.0** and **`affinidi-tsp-dart` 0.1.0**. The suite asks three questions — do the specification's Appendix A vectors open (and re-pack byte for byte where the vector publishes its ephemeral material), do all pairs interoperate across roughly 35 message shapes, and do they refuse what they should and run the relationship protocol correctly. The 2026-09-16 run: + +| Suite | Pass | Fail | +|---|---|---| +| Spec vectors | 152 | 0 | +| Pairwise interop | 893 | 0 | +| Relationship protocol | 250 | 0 | +| Negative cases | 377 | 8 | + +All eight negative failures are the *reference* `tsp_sdk` accepting a message it should refuse (trailing bytes, a non-zero CESR lead byte, a signed-only message whose ESSR sender does not match the envelope); none is in `affinidi-tsp`. The capability matrix shows `vti-tsp-js` does not yet implement signed-only messages, the legacy sealed box, or the post-quantum HPKE path. The TDK's own `docs/tsp/interop.md` now points at this suite as "the authoritative result" and keeps its older single-graph round-trip harness against `tsp_sdk` 0.10.0 only as a quick local check — with the standing instruction to re-run the suite on every reference release. One caveat travels with every implementation: the post-quantum CESR code point `1AAQ` is provisional (CESR issue #14), which is the main reason `pq` stays off by default. + +## Relationship to DIDComm, After the Bridge + +Under Rev 2 the mediator could *bridge*: re-pack a TSP message as DIDComm and back, so a TSP-only sender could reach a DIDComm-only recipient. Rev 3's ESSR makes that "by construction a point where the message is decrypted and re-signed by someone who is not the sender", so the bridge was removed and **both ends of a conversation must be on one protocol**. What interop looks like now: + +- **Two carriers on one mediator.** The TDK mediator still serves TSP and DIDComm on the same endpoint and the same per-DID WebSocket, sniffing which is which at ingress. Federation across independent mediators works for both (`docs/multi-mediator.md`): a TSP routed hop is sealed to the next mediator and signed by the previous one, so it is "rewrap-like by construction" and needs no relay-mode choice, and the peer-mediator allowlist applies to it with a *stronger* guarantee than DIDComm because every hop is authenticated. See [[didcomm]] for the DIDComm side of federation. +- **The choice is per peer, from the DID document.** A sender picks TSP when the peer advertises a `TSPTransport` service (matched by type, never by `#id` fragment) and the sender can carry it; otherwise DIDComm; otherwise REST. VTI's one outbound seam (#1474) and the fix in #1560 — select by the sender's *live* capability, not the build feature — are that rule made concrete. Advertisement is not availability: the browser wallet reports the transport that actually carried a byte. +- **TSP-only is a supported shape.** A VTA can run with no DIDComm compiled in; since mediator 0.22.3 the mediator answers its own management Trust Tasks over TSP (#783), so a TSP-only client can set its own ACL; a DIDComm/TSP-only VTA can be backed up over chunked Trust Tasks. +- **What stays DIDComm.** Interop with the wider Aries/Credo world (the mediator speaks DIDComm v1 for that), the did:webvh witness, and any peer that has not advertised TSP. On the same socket, TSP carries Trust Tasks while DIDComm carries protocol messages — a split VTI's decision note calls "load-bearing and understood", and one it refuses to fragment further with per-surface carve-outs. The stated long-term goal is still to deprecate DIDComm. + +## How We Got Here — Rev 2, June–August 2026 + +TSP's promotion happened in a coordinated burst across three repos in June–July 2026, against **Rev 2** (the November 2025 Experimental Implementer's Draft): + +- **The TDK did the heavy lifting** (~60 commits, `affinidi-tsp` 0.1.1 → 0.1.12): a DID-document VID resolver, Routed/Nested modes, ingress sniffing, the mediator's own TSP identity, routed relay and remote forwarding, the (since-removed) TSP↔DIDComm bridge, `TSPTransport` advertised in the mediator's DID document and baked into generated DIDs, CESR/HPKE compliance and full `tsp_sdk` wire parity, and graduation from *experimental* to **supported** with TSP-preferred selection and DIDComm fallback in `send_to`. +- **VTI first deferred, then reversed** — a 2026-06-22 decision record deferred TSP because the mediator could not route it; three days later the TDK work landed and an SDD (`tsp-enablement.md`) flipped guidance to **TSP > DIDComm > REST**. TSP shipped as a first-class managed service (`pnm services`, `TSPTransport` in DID templates, a TSP health probe) over the same mediator socket as DIDComm. By August it was a *selectable* transport (`TransportChoice::{Tsp, Didcomm, Auto}`), a VTA could run **TSP-only** (#937), the setup wizard offered it, and communities chose and published their transports at setup. +- **did-hosting-service** added TSP as a third transport binding in one PR, because every wire operation there is already a transport-agnostic Trust Task. +- **OpenVTC** ran the join ceremony over TSP when the community offered it, choosing TSP only when its *own* mediator could carry it; the browser wallet shipped the pure-TypeScript `vti-tsp-js` 0.2.0. + +Design decisions locked then and still true: DIDs double as TSP VIDs **reusing the existing Ed25519/X25519 keys** (no new key material — which is also why the VID model is not yet post-quantum); capability discovery is DID-document-driven; a VTA's `#tsp` service endpoint is its **mediator's DID**, so TSP needs a mediator but does not need DIDComm; and the ecosystem uses upstream routed/nested sends rather than building onion routing itself. + +## Open Items + +The VID model does not carry post-quantum keys, so nothing above `affinidi-tsp` can use the `pq` path yet; D7/D9 relationship hardening; the browser wallet's Rev 2 decode-only codec awaits deletion; `vti-tsp-js` lacks signed-only, sealed-box and PQ; the version-field reading is raised upstream; and the multi-hop nested path is exercised on cross-mediator sends but not yet driven from DID-document route discovery. + +See also: [[didcomm]], [[post-quantum-cryptography]], [[affinidi-tdk]], [[verifiable-trust-infrastructure]], [[verifiable-trust-agent]], [[coordinated-releases]] diff --git a/content/concepts/trust-task-context-binding.md b/content/concepts/trust-task-context-binding.md index 58f4cca..c12736d 100644 --- a/content/concepts/trust-task-context-binding.md +++ b/content/concepts/trust-task-context-binding.md @@ -2,8 +2,8 @@ title: "Trust Task Context Binding (taskContext)" type: concept tags: [credentials, dtg, trust-tasks, spec, witness] -date-updated: 2026-08-19 -sources: [dtg-credential-spec, dtg-credentials] +date-updated: 2026-09-18 +sources: [dtg-credential-spec, dtg-credentials, verifiable-trust-infrastructure, openvtc] --- # Trust Task Context Binding (`taskContext`) @@ -12,23 +12,23 @@ Most DTG credentials are produced *inside a protocol exchange* — a witnessed V ## The Property -Every DTG credential's base structure MAY carry **`taskContext`**: the identifier (`threadId`) of the trust-task exchange in which the credential was issued. It is OPTIONAL on all six types and **REQUIRED on the [[witness-credential|Witness Credential]]** — a VWC "MUST be bound to the trust task exchange in which it was issued." +Every DTG credential's base structure MAY carry **`taskContext`**: the identifier (`threadId`) of the trust-task exchange in which the credential was issued. It is OPTIONAL on every type and **REQUIRED on the [[witness-credential|Witness Credential]]** — a VWC "MUST be bound to the trust task exchange in which it was issued." (Working Draft 02 grew the catalogue to eight types with the [[delegation-credential|VDC]] and [[authority-credential|VAC]]; on the spec's `main` branch the requirement is phrased as "REQUIRED where a credential type *or a [[statement-credential|VSC]] predicate profile* says so", and the `dtg:witnessed` profile does.) ## The Rules 1. **Standalone interpretability.** A DTG credential *without* `taskContext` MUST be interpretable standing alone. The property adds context; it never becomes a dependency. 2. **Outcome interpretability.** A verifier MUST NOT interpret a `taskContext`-bearing credential as proof that the associated trust task or ceremony *completed* unless the matching trust-task outcome evidence is also present and verified. Knowing *which* exchange a credential came from is not knowing *how it ended*. -The spec offers an informative test for the boundary the rule protects — **credential or artifact?** "True outside the exchange? → credential. Only meaningful inside the exchange? → artifact." Credentials go in the DTG; artifacts (receipts, verdicts, completion records) belong to the trust-task protocol and its planned companion spec, *DTG Core Trust Task Protocols*, which will define offer / issue / request / present / revoke and the completion artifact that `taskContext` points at. +The spec offers an informative test for the boundary the rule protects — **credential or artifact?** "True outside the exchange? → credential. Only meaningful inside the exchange? → artifact." Credentials go in the DTG; artifacts (receipts, verdicts, completion records) belong to the trust-task protocol and its planned companion spec, *DTG Core Trust Task Protocols*, which will define offer / issue / request / present / revoke and the completion artifact that `taskContext` points at. WD02's [[delegation-credential|VDC]] is the clearest illustration of the boundary: the delegation *grant* ("this delegator appointed this delegate, for this scope, until then") is true standing alone and is a credential; the *invocation* ("the delegate is acting in the delegator's name, now, to do this") is meaningful only inside its exchange and is an artifact. The same split applies to exercising a [[authority-credential|VAC]] — how a presenter proves key control at invocation is the trust task's business, not the credential's. The spec's `main` branch now states the minimum companion version this binding depends on: Trust Tasks Document Status 0.4.0. ## Why It Exists -The witnessed-exchange work made the gap visible: a witness observes *a session*, and the [[witnessed-vrc-exchange]] protocol had always carried a session/thread id — but nothing in the credential model let a VWC say which session it attests, so a VWC could float free of the exchange it came from. `taskContext` is that anchor. Paired with the now-REQUIRED `digest` (a JCS/SHA-256 hash of the specific VRC being witnessed), a VWC identifies *the exchange* and *the edge*; `credentialSubject.id` identifies the observed party. +The witnessed-exchange work made the gap visible: a witness observes *a session*, and the [[witnessed-vrc-exchange]] protocol had always carried a session/thread id — but nothing in the credential model let a VWC say which session it attests, so a VWC could float free of the exchange it came from. `taskContext` is that anchor. Paired with the REQUIRED `digestMultibase` (a JCS/SHA-256 multihash of the specific edge credential being witnessed, excluding its `proof`), a VWC identifies *the exchange* and *the edge*; `credentialSubject.id` identifies the observed party. The rule against outcome-inference is the other half: it stops verifiers from treating "this VMC names join ceremony X" as "join ceremony X succeeded" — the success evidence is a separate, verifiable artifact. ## In the Implementation -[[dtg-credentials]] 0.2.0 added `taskContext` to `DTGCommon` and made it required on VWC construction and deserialisation (`MissingTaskContext`). This fixed a live bug: before 0.2.0 the field was silently dropped on deserialise, so issuers signed one document and verifiers hashed another. In the [[verifiable-trust-infrastructure|VTI]] and [[openvtc]], the thread id is simply the Trust-Task document id the ceremony already uses. +[[dtg-credentials]] 0.2.0 added `taskContext` to `DTGCommon` and made it required on VWC construction and deserialisation (`MissingTaskContext`). This fixed a live bug: before 0.2.0 the field was silently dropped on deserialise, so issuers signed one document and verifiers hashed another. In the [[verifiable-trust-infrastructure|VTI]] and [[openvtc]], the thread id is simply the Trust-Task document id the ceremony already uses — and since VTI #1178 the **VTC enforces the binding**, rather than merely carrying the field. See also: [[witness-credential]], [[witnessed-vrc-exchange]], [[dtg-credentials-overview]], [[verifiable-credentials]] diff --git a/content/concepts/verifiable-credentials.md b/content/concepts/verifiable-credentials.md index cee3a0f..498efdf 100644 --- a/content/concepts/verifiable-credentials.md +++ b/content/concepts/verifiable-credentials.md @@ -1,9 +1,9 @@ --- title: "Verifiable Credentials (VCs)" type: concept -tags: [credentials, w3c, standards, trust] -date-updated: 2026-08-19 -sources: [dtg-credentials, verifiable-trust-infrastructure, openvtc] +tags: [credentials, w3c, standards, trust, post-quantum] +date-updated: 2026-09-18 +sources: [dtg-credentials, verifiable-trust-infrastructure, openvtc, affinidi-tdk] --- # Verifiable Credentials (VCs) @@ -49,4 +49,6 @@ The [[affinidi-tdk|Affinidi TDK]] also supports **Selective Disclosure JWTs (SD- The [[verifiable-trust-agent|VTA]]'s vault is format-plural: W3C Data Integrity proofs (EdDSA JCS/RDFC 2022, BBS-2023, and since August 2026 `ecdsa-jcs-2019` for P-256 keys), SD-JWT, and — as of the `Cypress` release — **ISO 18013-5 mdoc**, received and verified against configured IACA trust anchors and presented over OpenID4VP with an ISO 18013-7 session transcript. DTG credentials themselves are W3C VCs (v2.0 primary, v1.1 legacy) signed with Data Integrity. -See also: [[dtg-credentials-overview]], [[decentralized-identifiers]], [[decentralized-trust-graph]] +**Hybrid, multi-proof credentials (September 2026).** A Data Integrity `proof` may be an *array*, and the ecosystem now uses that for the [[post-quantum-cryptography|post-quantum]] transition: a VTC signs a credential **with every key it holds**, one proof per key, each under the cryptosuite that key's type selects — an Ed25519 `eddsa-jcs-2022` proof beside an ML-DSA-44 **`mldsa44-jcs-2024`** (or `mldsa44-rdfc-2024`) proof from the W3C Quantum-Resistant Cryptosuites draft. A verifier that understands only classical suites accepts the credential on the Ed25519 proof; one that requires post-quantum can reject that proof and accept the ML-DSA one. Every VTC verification path reads a proof *set*, and a single-key VTC still emits a bare proof object, so nothing downstream changed until a second key existed. The suites live in the TDK's `affinidi-data-integrity` behind its `ml-dsa` feature (which VTI pins on); only ML-DSA-44 has a Data Integrity suite because that is the parameter set the W3C draft specifies. + +See also: [[dtg-credentials-overview]], [[decentralized-identifiers]], [[decentralized-trust-graph]], [[post-quantum-cryptography]] diff --git a/content/concepts/verifiable-trust-community.md b/content/concepts/verifiable-trust-community.md index 3ca42f6..7d64c9d 100644 --- a/content/concepts/verifiable-trust-community.md +++ b/content/concepts/verifiable-trust-community.md @@ -2,7 +2,7 @@ title: "Verifiable Trust Communities (VTCs)" type: concept tags: [vtc, community, trust, membership] -date-updated: 2026-08-19 +date-updated: 2026-09-18 sources: [verifiable-trust-infrastructure, openvtc, dtg-credential-spec] --- @@ -16,7 +16,9 @@ VTCs create denser clusters of trust within the broader [[decentralized-trust-gr ## How They Work -A VTC is identified by its own [[did-types|C-DID]] (Community DID) and has a community-level service — the **VTC Service** — that coordinates community operations. Members hold [[membership-credential|Membership Credentials]] issued by the community. When a VTC's governance enforces personhood guarantees, these VMCs qualify as [[personhood-credential|Personhood Credentials]] — determined by the community's [[trust-registries|trust registry]]. +A VTC is identified by its own DID — since Working Draft 02 of the spec no longer called a "C-DID" ([[did-types]] is retired), but simply the community's identifier, which can only truthfully be declared with a **`public`** [[correlation-scope]]: a community that cannot be found cannot be joined. It has a community-level service — the **VTC Service** — that coordinates community operations. Members hold [[membership-credential|Membership Credentials]]: a grant from the community and an acknowledgement back from the member, under an identifier and scope *the member* chooses for that membership. When a VTC's governance enforces personhood guarantees, the grants qualify as [[personhood-credential|Personhood Credentials]] — determined by the community's [[trust-registries|trust registry]]. + +WD02 also puts one normative duty on every VTC that issues VMCs: it **MUST publish**, in its governance framework or trust registry, whether member identifiers are disclosed beyond the VTA and to whom — because a member's `pairwise` declaration only stays truthful if the community keeps it so, and a verifier MUST NOT assume it does. A VTA SHOULD show the community's answer to a prospect before they choose a scope for joining. VTCs can belong to [[verifiable-trust-network|Verifiable Trust Networks (VTNs)]] — higher-level federations that enable trust paths to cross community boundaries. @@ -47,7 +49,9 @@ The [[verifiable-trust-infrastructure|VTI]] workspace includes a **VTC Service** - **PNM CLI** (Personal Network Manager) — for individual participation in communities - the **[[openvtc|OpenVTC TUI]]** — the member-side client: join by DID or agent name, present a VIC, see which capabilities a community has enabled, hold many memberships under distinct personas -Since mid-2026 a community **chooses and publishes the transports it offers** (TSP, DIDComm, REST) and which trust registry is authoritative for it, and its admin console (`/admin`, passkey-protected) approves joins, issues the member's VMC + role VEC, and manages the ACL. The spec's Working Draft 01 names the bootstrap roles: an **initiator** generates the C-DID, instantiates the community VTA and invites **community trust anchors (CTAs)**, who are automatically issued VMCs; a **Policy Enforcement Point (PEP)** enforces the community's issuance/revocation policies. See [[vta-topology]] for how a community is served by a *network* of its members' agents. +Since mid-2026 a community **chooses and publishes the transports it offers** (TSP, DIDComm, REST) and which trust registry is authoritative for it, and its admin console (`/admin`, passkey-protected) approves joins, issues the member's VMC + role VEC, and manages the ACL. The spec names the bootstrap roles: an **initiator** generates the community's identifier, instantiates the community VTA and invites **community trust anchors (CTAs)**, who are issued VMCs (and, since WD02, acknowledge them like any other member); a **Policy Enforcement Point (PEP)** enforces the community's issuance/revocation policies. See [[vta-topology]] for how a community is served by a *network* of its members' agents. + +**Peer identity vetting.** Since Dogwood, [[openvtc|OpenVTC]] carries a V0 of community-run identity vetting (openvtc #292–#343, design DRAFT v3): the community names **vetters** by issuing them a revocable `CommunityRole` [[endorsement-credential|VEC]], publishes a vetter directory, and hands out QR tickets that a prospect brings to a vetter; the vetter's attestation becomes evidence for the community's personhood decision. See [[peer-identity-vetting]]. This is the pattern the spec's `main` branch formalises as [[statement-credential|statement credentials]] weighed by governance — and, for what a vetter *may do*, an [[authority-credential|authority credential]] rather than an endorsement. ## Trust Policies diff --git a/content/concepts/verifiable-trust-network.md b/content/concepts/verifiable-trust-network.md index ed3fab3..13823ab 100644 --- a/content/concepts/verifiable-trust-network.md +++ b/content/concepts/verifiable-trust-network.md @@ -2,7 +2,7 @@ title: "Verifiable Trust Networks (VTNs)" type: concept tags: [vtn, network, community, hierarchy, trust] -date-updated: 2026-08-19 +date-updated: 2026-09-18 sources: [dtg-credential-spec] --- @@ -12,7 +12,7 @@ sources: [dtg-credential-spec] A Verifiable Trust Network is a higher-level grouping above [[verifiable-trust-community|Verifiable Trust Communities (VTCs)]]. If a VTC is like a club or professional association, a VTN is like a federation of clubs — an umbrella organization that coordinates multiple communities under shared governance. -VTNs have their own [[decentralized-identifiers|C-DIDs]] (Community DIDs), just like VTCs. The hierarchy works like this: +VTNs have their own [[decentralized-identifiers|DIDs]], just like VTCs — necessarily declared with a `public` [[correlation-scope]] (the old "C-DID" label was retired in Working Draft 02). The hierarchy works like this: ``` VTN (Verifiable Trust Network) @@ -28,7 +28,7 @@ VTN (Verifiable Trust Network) ## How Membership Works -A VTN's relationship with each member VTC is expressed as a bi-directional pair of [[membership-credential|Membership Credentials (VMCs)]] — one issued by the VTN to the VTC, one issued by the VTC back to the VTN. This is the same credential type used for individual membership in a VTC, but the subject is a C-DID (the community's DID) rather than an M-DID (a person's DID). +A VTN's relationship with each member VTC is expressed as a bi-directional pair of [[membership-credential|Membership Credentials (VMCs)]] — a grant issued by the VTN to the VTC, and an acknowledgement issued by the VTC back to the VTN carrying a digest of the grant. This is the same credential type used for individual membership in a VTC; because both ends are communities, the issuer/subject rules cannot tell the directions apart, so the presence of `digestMultibase` is the discriminator (WD02). This means the [[decentralized-trust-graph|Decentralized Trust Graph]] supports hierarchical community structures: a VTN contains VTCs, and VTCs contain individual members. @@ -42,8 +42,12 @@ For [[invitation-credential|Invitation Credentials (VICs)]], VTNs can authorize VTNs enable trust to scale beyond individual communities. If two people belong to different VTCs within the same VTN, trust paths can traverse the network structure — the VTN's governance provides a shared trust anchor. This is particularly relevant for large ecosystems like the [[first-person-network|First Person Network]], where many independent communities need to interoperate. -## Vocabulary (WD01) +## Edge Verifiability: the VTN as one anchor set among many (WD02) -Working Draft 01 of the [[dtg-credential-spec|spec]] gives the VTN its own glossary entries: a **VTN trust anchor** is a VTC that anchors a VTN; **VTN members** are VTCs; the **VTN invitation credential** is the [[invitation-credential|VIC]]'s second functional variant, issued to a prospective member VTC's C-DID; and [[trust-registries|trust registries]] are where VTN anchors are discovered. The First Person Network is the canonical example of a VTN. +Working Draft 01's glossary said a VRC was "only verifiable as a DTG edge in the context of a specific VTN" if both peers used M-DIDs, both had signed the VRC, and their VTCs were VTN trust anchors. Two implementations reported that this contradicted the body and that one condition — both peers signing one credential — could not be satisfied by any credential. WD02 (spec PR #26) replaced it with a normative **Edge Verifiability** section that defines the property **relative to a verifier**: an edge credential is verifiable as a DTG edge *by a given verifier* when its proof verifies and the verifier can establish that its issuer's membership in a VTC **in the anchor set that verifier accepts** is complete — by disclosure of the VMC pair or by a community-anchored [[zero-knowledge-proofs|ZKP]]. A VTN is the *common* case of such an anchor set, but no VTN need exist, and an edge whose halves trace to VTCs in different VTNs (or none) is still an edge to any verifier whose anchor set includes both. The same credential may be an edge to one verifier and not another, and neither is wrong. This is the per-verifier phrasing the ZKP task force's reference circuits already use. + +## Vocabulary + +The [[dtg-credential-spec|spec]] gives the VTN its own glossary entries: a **VTN trust anchor** is a VTC that anchors a VTN; **VTN members** are VTCs; the **VTN invitation credential** is the [[invitation-credential|VIC]]'s second functional variant, issued to a prospective member VTC's identifier; and [[trust-registries|trust registries]] are where VTN anchors are discovered. The First Person Network is the canonical example of a VTN. See also: [[verifiable-trust-community]], [[membership-credential]], [[decentralized-trust-graph]], [[invitation-credential]] diff --git a/content/concepts/vta-topology.md b/content/concepts/vta-topology.md index 9b4bfd8..68f3f07 100644 --- a/content/concepts/vta-topology.md +++ b/content/concepts/vta-topology.md @@ -2,8 +2,8 @@ title: "VTA Topology — personal, community, local, cloud" type: concept tags: [vta, dtg, topology, pnm, spec, architecture] -date-updated: 2026-08-19 -sources: [dtg-credential-spec, verifiable-trust-infrastructure, vta-browser-plugin, vti-setup] +date-updated: 2026-09-18 +sources: [dtg-credential-spec, verifiable-trust-infrastructure, vta-browser-plugin, vti-setup, vtafarm, vtafarm-api, vtafarm-k8s] --- # VTA Topology — personal, community, local, cloud @@ -42,11 +42,12 @@ A **Verifiable Trust Service Provider (VTSP)** provisions local VTAs, hosts clou | Spec term | In the ecosystem today | |-----------|------------------------| -| Cloud personal VTA | `vta-service` ([[verifiable-trust-infrastructure]]) — self-hosted per [[vti-setup]], or managed on the **VTA Farm** (vtafarm.firstperson.dev, open signup since July 2026; Kubernetes deployment docs coming) | -| Local personal VTA / PNM | the `pnm` CLI; the Authenticator + PNM mobile apps on `vta-mobile-core` (e.g. the iOS `vta-mobile-agent`); the [[vta-browser-plugin|VTA Wallet browser plugin]] (`@openvtc/pnm-core`) | +| Cloud personal VTA | `vta-service` ([[verifiable-trust-infrastructure]]) — self-hosted per [[vti-setup]], or managed on the **[[vtafarm|VTA Farm]]** (vtafarm.firstperson.dev, open signup since July 2026). Since August 2026 the Farm itself is open source: the portal ([[vtafarm]]), the Go/Kubernetes provisioning backend ([[vtafarm-api]]) and the OpenTofu cluster build ([[vtafarm-k8s]]) — one Kubernetes namespace per user, every master seed sealed in HashiCorp Vault, and the admin key generated on the user's own machine so the operator holds the agent but never controls it | +| Local personal VTA / PNM | the `pnm` CLI (also shipped as a keyring-free `pnm-server` build for headless hosts); the Authenticator + PNM mobile apps on `vta-mobile-core` (e.g. the iOS `vta-mobile-agent`); the [[vta-browser-plugin|VTA Wallet browser plugin]] (`@openvtc/pnm-core`) — which since September 2026 also carries a management console, so one surface is both a PNM and an operator console | | Personal Network Vault | the VTA's credential vault (`vta-vault`) and key store (`vta-keys`), presented through the PNM surfaces | | Community VTA / CNM | `vtc-service` + the `cnm` CLI and admin UI; [[openvtc]] acts as the member-side client | -| VTSP | the project's own hosted stack (VTA Farm, mediator, DID hosting) — and, by design, any operator who stands up [[vti-setup]]'s sysop stream | +| A node that is neither a person nor a community | a **[[data-rooms|data room]]** — its own DID, its own credentials, hosted by `vtc-service` or the standalone `room-host` (which can itself be enrolled with and governed by a VTA, like a mediator or DID host) | +| VTSP | the project's own hosted stack (VTA Farm, mediator, DID hosting) — and, by design, any operator who stands up [[vti-setup]]'s explore stream or builds their own Farm from [[vtafarm-k8s]] | | VTA network endpoint | the `#tsp` / `#didcomm` / `#rest` services in the DID document (capability discovery is DID-document-driven; TSP preferred) | ## Why the Vocabulary Matters diff --git a/content/concepts/witness-credential.md b/content/concepts/witness-credential.md index 8c4fbf4..fed88b0 100644 --- a/content/concepts/witness-credential.md +++ b/content/concepts/witness-credential.md @@ -2,13 +2,13 @@ title: "Witness Credential (VWC)" type: concept tags: [credentials, dtg, witness, attestation] -date-updated: 2026-08-19 +date-updated: 2026-09-18 sources: [dtg-credential-spec, dtg-credentials] --- # Witness Credential (VWC) -A Verifiable Witness Credential is a third-party attestation that adds credibility to claims in the [[decentralized-trust-graph|Decentralized Trust Graph]]. A witness says "I observed that this specific relationship was established, in this specific exchange." +A Verifiable Witness Credential is a third-party attestation that adds credibility to claims in the [[decentralized-trust-graph|Decentralized Trust Graph]]. A witness says "I observed that this specific edge credential was issued, in this specific exchange." ## What Makes Witnesses Powerful @@ -18,26 +18,31 @@ Two-party claims (like [[relationship-credential|VRCs]]) are only as trustworthy - **Event attestation** — "I witnessed this transaction/interaction at this event" - **Sybil resistance** — it's hard to fake a witness who was physically present -Since Working Draft 01 of the [[dtg-credential-spec|spec]] the witness need not be a person: it may be **a VTA applying the witnessing policies of a VTC** — for example verifying that both parties were present at the same event, or that each provided proof of biometric liveness. The issuer is therefore an M-DID or the DID of a VTA acting under community policy (the old separate "W-DID" type was dropped — see [[did-types]]). +The witness need not be a person: it may be **a VTA applying the witnessing policies of a VTC** — verifying that both parties were at the same event, or that each gave proof of biometric liveness. The issuer is the witness's own identifier — a member's, or a VTA's under community policy — which under WD02's [[correlation-scope]] must be **`directed` at minimum**: a witness has to be recognisable to both parties and to the community whose policy applies, so `pairwise` cannot describe it truthfully. (The old separate "W-DID" type was dropped in WD01; see [[did-types]].) -## What It Contains (WD01) +## What It Contains — Working Draft 02 (what implementations use) Beyond standard VC fields: + - **`taskContext`** — REQUIRED: the thread identifier of the trust-task exchange in which the VWC was issued. A VWC "MUST be bound to the trust task exchange in which it was issued." See [[trust-task-context-binding]]. -- **`credentialSubject.id`** — REQUIRED: the DID of the **issuer of the VRC being attested** (the observed party), not the recipient. -- **`credentialSubject.digest`** — REQUIRED (since spec PR #14, 2026-08-12): the SHA-256 hash of the attested VRC's JSON, canonicalised with JCS (RFC 8785), encoded as the string `sha256:` followed by the lowercase hex digest. This is what binds the VWC to *a specific edge* rather than to "some activity involving the subject during a trust task." +- **`credentialSubject.id`** — REQUIRED: the DID of the **issuer of the edge credential being attested** (the observed party), not its recipient. +- **`credentialSubject.digestMultibase`** — REQUIRED: a digest of the witnessed edge credential — SHA-256 over its JCS (RFC 8785) canonical form **excluding the top-level `proof`**, wrapped as a `sha2-256` multihash and base58btc-encoded with a `z` prefix (the W3C `digestMultibase` encoding). This binds the VWC to *a specific edge* rather than to "some activity involving the subject during a trust task." Verifiers compare decoded bytes, never strings. WD01 called the property `digest` and encoded it `sha256:`; WD02 (spec #19) changed both. - **`witnessContext`** — OPTIONAL: event name, session ID, verification method (e.g., "in-person-proximity", "video-call"). -**One VWC per direction.** A witnessed exchange of a complete edge is bidirectional — two VRCs, one each way, in a single witnessing event — so the witness SHOULD issue one VWC per direction, each naming the issuer of the VRC it attests. See [[witnessed-vrc-exchange]]. +**One VWC per direction, for VRC pairs and VMC pairs alike.** A witnessed exchange of a complete edge is bidirectional — two edge credentials, one each way, in a single witnessing event. Since WD02 this applies to a membership edge as well as a peer edge: the VWC for the community's grant names the community; the VWC for the member's acknowledgement names the member. See [[witnessed-vrc-exchange]]. + +**The binding is only as strong as access to the referenced credential.** `credentialSubject.id` and `taskContext` identify the observed party and the exchange, not the edge; a digest without the referenced credential to hand is an opaque hash. Issuers and holders presenting a VWC as evidence of a specific edge SHOULD make that credential available alongside it. Because the digest excludes `proof` and status, a VWC attests the *claims* at the moment of witnessing — not that the credential is still valid or unrevoked. + +## Where It Is Going — `main` (Working Draft 0.4.0, not yet tagged or implemented) -**The binding is only as strong as access to the VRC.** `credentialSubject.id` and `taskContext` identify the observed party and the exchange, not the edge; a `digest` without the referenced VRC to hand is an opaque hash. Issuers and holders presenting a VWC as evidence of a specific edge SHOULD make the referenced VRC available alongside it. +On the spec's `main` branch (2026-09-15) the VWC is no longer a concrete type. It is the **`dtg:witnessed` predicate profile of the [[statement-credential|Verifiable Statement Credential]]**: `type` is `StatementCredential`, `credentialSubject.predicate` is `https://firstperson.network/credentials/dtg/v1#witnessed`, and the digest moves to `credentialSubject.object.digestMultibase`. Everything above carries over — `taskContext` REQUIRED, `directed` minimum scope, one VWC per direction, `witnessContext` — with one tightening: **direction binding is unconditional**. WD02 required "subject = issuer of the referenced credential" only for bidirectional exchanges; the profile makes it a rule for every VWC, because a verifier holding a VWC and its referenced credential cannot tell whether that credential was half of a pair. "Witnessed a party *present* or *hold* a credential" is a different predicate. The profile also says what a pass does *not* establish: that the credential is current, that its claims are true, that the exchange completed, that the witness is a member, or that anything is authorised. -## Implementation Note +## Implementation Status -The [[dtg-credentials]] crate (0.2.0) implements `taskContext` and digest helpers but encodes the digest as a multibase base58btc multihash (`z…`, the W3C `digestMultibase` convention) rather than the spec's `sha256:` — same hash, different string, not interoperable in either direction. Flagged in the crate's README as unresolved; to be raised with the DTGWG. The crate also still treats `digest` as optional, which predates spec PR #14. +[[dtg-credentials]] 0.7.0+ implements the WD02 shape: `CredentialSubjectWitness { id, digest_multibase, witness_context }`, serialising as `digestMultibase` and still accepting the WD01 name `digest` on parse; `verify_digest()` compares decoded bytes. The 0.2.0 encoding divergence recorded here previously is closed (the crate moved to the hex form in 0.4.0, then the spec moved to multibase in WD02, adopted in 0.7.0). One gap remains: the crate still treats the digest as **optional** — `new_vwc(.., digest: Option, ..)`, `Option` on the struct, no `MissingDigest` error — although the spec has required it since WD01 PR #14. The VSC form is not implemented. ## Role in Trust Assessment -When traversing the trust graph, witnessed relationships carry more weight than unwitnessed ones. A relationship with an in-person witness attestation from a well-known conference is significantly stronger than a purely online claim. Communities can encode this into their [[verifiable-trust-community|trust policies]]. +When traversing the trust graph, witnessed relationships carry more weight than unwitnessed ones. A relationship with an in-person witness attestation from a well-known conference is significantly stronger than a purely online claim. Communities can encode this into their [[verifiable-trust-community|trust policies]] — and on `main` the spec classifies a witness statement explicitly as *evidence*, weighed by the community whose witnessing policy it was issued under. -See also: [[dtg-credentials-overview]], [[relationship-credential]], [[decentralized-trust-graph]], [[trust-task-context-binding]] +See also: [[dtg-credentials-overview]], [[relationship-credential]], [[membership-credential]], [[witnessed-vrc-exchange]], [[trust-task-context-binding]], [[statement-credential]] diff --git a/content/concepts/witnessed-vrc-exchange.md b/content/concepts/witnessed-vrc-exchange.md index 7c0608d..efe1355 100644 --- a/content/concepts/witnessed-vrc-exchange.md +++ b/content/concepts/witnessed-vrc-exchange.md @@ -2,13 +2,13 @@ title: "Witnessed VRC Exchange Protocol" type: concept tags: [protocol, witness, vrc, flow, didcomm] -date-updated: 2026-08-19 +date-updated: 2026-09-18 sources: [dtg-credential-spec, dtg-credentials] --- # Witnessed VRC Exchange Protocol -The Witnessed Session-Based VRC Exchange (v0.2, kept as supporting material in the DTG task-force repo; its per-direction VWC pattern became normative in the [[dtg-credential-spec|spec]]'s Working Draft 01) is a five-phase protocol for creating [[relationship-credential|Relationship Credentials (VRCs)]] with third-party [[witness-credential|Witness]] attestation. It ensures that both the relationship and the witness proof are cryptographically bound to a specific session. +The Witnessed Session-Based VRC Exchange (v0.2, kept as supporting material in the DTG task-force repo; its per-direction VWC pattern became normative in the [[dtg-credential-spec|spec]]'s Working Draft 01, and Working Draft 02 extended it to witnessed VMC pairs as well) is a five-phase protocol for creating [[relationship-credential|Relationship Credentials (VRCs)]] with third-party [[witness-credential|Witness]] attestation. It ensures that both the relationship and the witness proof are cryptographically bound to a specific session. ## Why Witnessed Exchange? @@ -39,7 +39,7 @@ The Witness performs three checks on each submission: ### Phase 4: Credential Distribution 1. The Witness mints two [[witness-credential|Witness Credentials (VWCs)]] — one for each observed VRC -2. Each VWC carries a `taskContext` naming this session's trust-task thread and (since WD01, REQUIRED) a `digest` of the witnessed VRC — SHA-256 over its JCS canonical form, encoded `sha256:` — with `credentialSubject.id` set to the issuer of that VRC; see [[witness-credential]] and [[trust-task-context-binding]] +2. Each VWC carries a `taskContext` naming this session's trust-task thread and a REQUIRED `digestMultibase` of the witnessed VRC — SHA-256 over its JCS canonical form *excluding `proof`*, as a base58btc multihash (WD02; WD01 used `digest` = `sha256:`) — with `credentialSubject.id` set to the *issuer* of that VRC; see [[witness-credential]] and [[trust-task-context-binding]]. On the spec's `main` branch the VWC is the `dtg:witnessed` profile of a [[statement-credential|Statement Credential]] and the digest sits under `object.digestMultibase`; the direction rule becomes unconditional 3. Distribution is cross-wise: Alice receives the VWC witnessing Bob's VRC, and Bob receives the VWC witnessing Alice's VRC ### Phase 5: Verification @@ -48,7 +48,7 @@ At the end, each party holds: - Their counterparty's VRC (the trust attestation) - A VWC from the Witness attesting that the VRC was created during the specific session -Anyone verifying the relationship later can check both the VRC (peer attestation) and the VWC (third-party attestation of the exchange). +Anyone verifying the relationship later can check both the VRC (peer attestation) and the VWC (third-party attestation of the exchange) — provided the VRC is to hand, since a digest without its referenced credential is an opaque hash. The witness's identifier must be `directed` at minimum ([[correlation-scope]]): it has to be recognisable to both parties and to the community whose witnessing policy applies. ## Trust Implications diff --git a/content/concepts/zero-knowledge-proofs.md b/content/concepts/zero-knowledge-proofs.md index 4c50891..2705964 100644 --- a/content/concepts/zero-knowledge-proofs.md +++ b/content/concepts/zero-knowledge-proofs.md @@ -2,86 +2,83 @@ title: "Zero-Knowledge Proofs in the DTG" type: concept tags: [zkp, privacy, credentials, dtg, presentation] -date-updated: 2026-08-19 +date-updated: 2026-09-18 sources: [dtg-credential-spec] --- # Zero-Knowledge Proofs in the DTG -DTG credentials are W3C-compliant Verifiable Credentials. They MAY be presented using standard VC presentation methods, but the [[dtg-credentials-overview|spec]] says they SHOULD be presented as **zero-knowledge proofs (ZKPs)** whenever privacy preservation matters — and that implementations SHOULD make ZKP presentation the default. This page explains why, and lays out the two distinct ZKP constructions the spec defines. +DTG credentials are W3C-compliant Verifiable Credentials. They MAY be presented using standard VC presentation methods, but the [[dtg-credential-spec|spec]] says they SHOULD be presented as **zero-knowledge proofs (ZKPs)** whenever privacy preservation matters — and that implementations SHOULD make ZKP presentation the default. This page explains why, lays out the two constructions the spec defines, and — new with Working Draft 02 — says plainly which of the spec's ZK predicates exist today and which are waiting on work not yet done. ## Why ZKPs Matter Here -The DTG is an attempt to build a trust graph that survives mass-scale impersonation by AI agents and Sybil attackers without becoming a surveillance graph in the process. A naïve presentation of a [[membership-credential|VMC]] reveals which community you belong to, your member DID, and (transitively) every relationship anchored to that DID. Once that's done a few times, your "trust profile" is correlatable across every counterparty you've ever proven anything to. +The DTG is an attempt to build a trust graph that survives mass-scale impersonation by AI agents and Sybil attackers without becoming a surveillance graph in the process. A naïve presentation of a [[membership-credential|VMC]] reveals which community you belong to, your identifier there, and (transitively) every relationship anchored to it. Once that's done a few times, your "trust profile" is correlatable across every counterparty you've ever proven anything to. -ZKPs sever that correlation. The holder proves what is necessary for the verifier — "I'm a member of *some* community in this VTN," "this persona is a real person," "I have a relationship with that persona" — without disclosing the underlying DIDs, credentials, or other claims that would enable correlation. The spec cites Adler et al. 2024, [Personhood Credentials](https://arxiv.org/pdf/2408.07892), which argues that ZKPs are the only inherently privacy-preserving option for proof of personhood with credentials of this kind. - -The "ZKPs by default" guidance in [Privacy Considerations](#privacy-considerations) follows directly: privacy preservation should not require any extra effort on the user's part. +ZKPs sever that correlation. The holder proves what is necessary — "I'm a member of *some* community in this VTN," "this persona is a real person," "I have a relationship with that persona" — without disclosing the underlying identifiers or credentials. The spec cites Adler et al. 2024, [Personhood Credentials](https://arxiv.org/pdf/2408.07892), which argues that ZKPs are the only inherently privacy-preserving option for proof of personhood with credentials of this kind. Hence "ZKPs by default": privacy should not require any extra effort on the user's part. ## Two Constructions -The spec defines two ZKP constructions for [[credential-categories|Edge Credentials]]. They differ in what they prove, what they require to construct, and what assurance they carry. - ### 1. Pairwise ZKP (anchored to the VRC) -**Where:** [[relationship-credential|VRC]], the spec's VRC section. - -**What it proves:** Possession of a valid VRC, with selective disclosure of chosen attributes, subject DIDs, or predicates over them. The canonical application is to disclose the parties' [[did-types|P-DIDs (Persona DIDs)]] while hiding the underlying [[did-types|R-DIDs]] — a public, verifiable claim that two known personas have a relationship, without exposing the private pairwise channel between them or enabling correlation across the holder's other presentations. - -**Who can construct it:** Any two parties who hold a VRC between them. **Shared community membership is not required.** Two entities that don't share (or don't hold) a VMC can still exchange VRCs and prove pairwise relationships through them. +**What it proves:** Possession of a valid [[relationship-credential|VRC]], with selective disclosure of chosen attributes, identifiers, or predicates. The canonical application is to disclose the parties' **`directed` persona identifiers** while hiding the underlying **`pairwise`** ones (see [[correlation-scope]]) — a public, verifiable claim that two known personas have a relationship, without exposing the private channel between them or enabling correlation across the holder's other presentations. -**What assurance it carries:** Whatever the parties bring to it. The proof itself stands on the cryptographic integrity of the VRC and the public reputation attached to any disclosed persona DIDs. It does not by itself confer community-level assurance like personhood — that requires the community-anchored construction. +**Who can construct it:** Any two parties who hold a VRC between them. Shared community membership is not required. -This construction is what makes the [[persona-credential|VPC's "Banksy Maneuver"]] viable: linking a pseudonymous P-DID to an existing relationship, selectively, to one counterparty. +**What assurance it carries:** Whatever the parties bring to it — the VRC's integrity and the public reputation of any disclosed persona. No community-level assurance such as personhood. This is what makes the [[persona-credential|VPC's "Banksy Maneuver"]] viable. ### 2. Community-Anchored ZKP (anchored to the VMC) -**Where:** [[membership-credential|VMC]], the spec's VMC section. +**What it proves:** (1) possession of the VRC; (2) possession of the holder's own community-issued VMC; (3) that the VRC issuer holds a VMC from the *same community identifier*. The relationship is proven within a shared community's governance context without revealing identifiers. -**What it proves:** A three-part proof of relationship-within-a-shared-community: +**Who can construct it:** Both parties must hold VMCs from the same community. -1. Possession of the VRC -2. Possession of the underlying VMC (proving membership in the community) -3. The VRC issuer also holds a VMC from the *same* [[did-types|C-DID]] +**What assurance it carries:** Whatever the community's [[trust-registries|trust registry]] attaches to its VMCs — most importantly personhood, when the VMCs are [[personhood-credential|PHCs]]. "Two real, unique humans within community X have a relationship," without saying which. -The relationship's existence is proven *within the governance context of a shared community* without revealing the specific DIDs or other credential details. - -**Who can construct it:** Both parties to the VRC must hold VMCs from the same community. - -**What assurance it carries:** Whatever assurances the community's [[trust-registries|trust registry]] attaches to its VMCs. Most importantly, when a community's VMCs qualify as [[personhood-credential|PHCs]], that personhood assurance carries forward into every relationship proven through this construction. This is the proof that says "two real, unique humans within community X have a relationship," without disclosing which humans, or which relationship. - -This is one proof construction available to relationships within a shared community — the spec is explicit that it is not the only one. Detailed ZK protocols and registry-ZK interactions are out of scope for the spec itself. +**Two WD02 caveats.** Statement 2 is the holder demonstrating their own grant, which the *Membership Edge Completion* rule accepts. Statement 3 rests on a grant whose subject is the *counterparty*, and a grant alone does not establish that its subject consented; until the trust-task and ZK work closes this, a verifier SHOULD read statement 3 as "the community attested the issuer's membership," not "the issuer acknowledged it." And where a party used one `pairwise` identifier toward the community and another toward the counterparty — the ordinary case under strict pairwise — the identifiers differ by construction, so the proof must additionally establish **common control** of both. ## Choosing Between Them | | Pairwise ZKP | Community-Anchored ZKP | |---|---|---| | **Requires shared VTC?** | No | Yes | -| **Anchored to** | VRC | VRC + VMC + same C-DID | -| **Hides** | R-DIDs (pairwise channel) | All DIDs and credential details | -| **Discloses (typical)** | P-DIDs, chosen predicates | Predicates only (e.g., "personhood") | -| **Personhood assurance?** | Only via disclosed P-DID's reputation | Yes, when the community's VMCs are PHCs | +| **Anchored to** | VRC | VRC + VMC + same community identifier | +| **Hides** | `pairwise` identifiers (the private channel) | All identifiers and credential details | +| **Discloses (typical)** | `directed` persona identifiers, chosen predicates | Predicates only (e.g., "personhood") | +| **Personhood assurance?** | Only via a disclosed persona's reputation | Yes, when the community's VMCs are PHCs | | **Best for** | Public, persona-level relationship proofs (Banksy) | Anonymous proof of *real-human* relationship within a community | -A holder may have both options available for a given counterparty, and choose based on what the verifier needs: if a verifier only needs "I have a relationship with this public persona," the pairwise construction is sufficient and exposes less. If a verifier needs "I have a relationship with a real human in community X," the community-anchored construction is the right tool. +Both routes are equal citizens: WD02's *Edge Verifiability* section lets the membership condition for an edge be met by disclosure *or* by the community-anchored proof, and calls the two equivalent in principle. -## ZKPs Across the Credential Family +## The Predicates the Schemas Are Shaped For -ZKP framing isn't limited to Edge Credentials. The spec's "ZKPs by default" guidance applies across all DTG credential presentation: +The spec keeps its schemas minimal so that common predicates can be proven. WD02 and `main` extend the list well beyond relationships: -- **VMCs** can be presented to prove community membership (and, where applicable, personhood) without revealing the specific M-DID -- **VRCs** support both ZKP constructions above -- **[[persona-credential|VPCs]]** rely on selective-disclosure semantics to link a P-DID to a relationship for one specific counterparty without disclosing it more broadly -- **[[endorsement-credential|VECs]]** and **[[witness-credential|VWCs]]** can be presented with predicate-only disclosure (e.g., "endorsed for skill X" without revealing endorser identity) +- "Holder has a valid community-issued VMC from a recognized VTC"; "Issuer is an authorized member"; "Two distinct VRCs exist" +- "Holder has a valid, unrevoked **delegation** to act in the name of a member of a recognized VTC, covering act X" — and "this [[delegation-credential|VDC]] chain is valid" without disclosing the chain +- "Holder holds a [[authority-credential|VAC]] conferring action X at scope S, and its chain is valid and unrevoked" without disclosing the chain +- "Two credentials presented together **share a subject**" — REQUIRED whenever membership and authority are both proven with the subject withheld, else two parties can pool one's membership with the other's authority +- *(main)* "Holder holds a [[statement-credential|statement credential]] from an issuer in set S, under predicate P, about the holder" — one construction for every VSC profile -Detailed ZK protocols, the binding to specific cryptographic schemes (BBS+, etc.), and registry-ZK interactions are out of scope for the credential spec; they belong to implementation profiles. +## What Is Waiting on the ZKP Task Force -## Privacy Considerations +A `main`-branch editor's note (2026-09-10) says what earlier drafts left implicit: these predicates are *requirements on the schemas*, not machinery that exists. **Four of them rest on one primitive no DTG specification yet defines — a proof that two credentials, or two identifiers, are under common control, without disclosing either** (spec issue #9): the community-anchored proof wherever VMC and VRC identifiers differ; the shared-subject requirement; and the ZK forms of both the VDC and VAC chains. Correlation scope makes the first the ordinary case rather than an edge case. Separately, none of the spec's digest-valued members is salted, so a digest over low-entropy content can be reversed by enumeration (issue #38). The ZKP task force's working draft and construction catalogue (records 007 common control, 010 community-anchored composition, 020 delegation chains, 008 blinded binders) bear on these but close nothing yet. + +**What holds meanwhile:** nothing in the spec is unverifiable — every requirement can be checked by presenting the credentials themselves. The cost is privacy, not correctness, and it falls hardest on chains, whose disclosure boundary is the whole chain. Implementations should not defer shipping a rule because its ZK form is unspecified. + +## ZKPs Across the Credential Family -From the spec's [Privacy Considerations](#privacy-considerations) section: +- **VMCs** — prove membership (and personhood) without revealing the member's identifier +- **VRCs** — both constructions above +- **[[persona-credential|VPCs]]** — selective disclosure linking a persona to one counterparty +- **VECs / VWCs** (on `main`, VSC profiles) — predicate-only disclosure, e.g. "endorsed for X" without the endorser; holders should be able to withhold `witnessContext` +- **VDCs / VACs** — chain validity and scope containment without ancestry disclosure (future); today a derived VDC or attenuated VAC reveals its whole chain, principal included + +The binding to specific schemes (BBS+, SD-JWT-VC, etc.) is out of scope for the credential spec. + +## Privacy Considerations > **ZKPs by default:** ZKP presentation should be used by default so that privacy preservation does not require any extra effort on behalf of users. The non-ZKP path remains available — the credentials are W3C-compliant — but it is the explicit fallback, not the norm. Implementations that ship standard VC presentation as the default are out of step with the spec's privacy posture. -See also: [[membership-credential]], [[relationship-credential]], [[persona-credential]], [[credential-categories]], [[trust-registries]], [[dtg-credentials-overview]] +See also: [[membership-credential]], [[relationship-credential]], [[persona-credential]], [[correlation-scope]], [[credential-categories]], [[trust-registries]], [[dtg-credentials-overview]] diff --git a/content/entities/affinidi-tdk.md b/content/entities/affinidi-tdk.md index 7a759de..6cbb1e3 100644 --- a/content/entities/affinidi-tdk.md +++ b/content/entities/affinidi-tdk.md @@ -1,8 +1,8 @@ --- title: "Affinidi Trust Development Kit (TDK)" type: entity -tags: [affinidi, tdk, library, messaging, did-resolution, bbs, openid4vc, agent-names, tsp, secondary, cypress] -date-updated: 2026-08-19 +tags: [affinidi, tdk, library, messaging, did-resolution, bbs, openid4vc, agent-names, tsp, did-webs, post-quantum, secondary, cypress, dogwood, eucalyptus] +date-updated: 2026-09-18 repo: https://github.com/affinidi/affinidi-tdk-rs --- @@ -17,15 +17,18 @@ The Affinidi TDK is a comprehensive Rust toolkit providing identity, messaging, ### DID Resolution (`affinidi-did-resolver`) High-performance [[decentralized-identifiers|DID]] resolution with local and network caching: - 250k+ resolutions per second from cache -- Pluggable DID method support (did:webvh, did:key, did:peer, did:scid) -- Integrates with [[didwebvh-rs]] for webvh verification +- Pluggable DID method support: did:webvh (via [[didwebvh-rs]]), did:web, did:key, did:peer, did:jwk, did:scid (`vh:1` and, since August 2026, `ke:1`), **did:webs** (KERI-verified key state, new August 2026), did:ethr / did:pkh (re-implemented in-tree, off by default), did:ebsi. did:cheqd *parses* but is no longer resolved (retired September 2026 to clear eight advisories rooted in the abandoned `ssi-*` stack) +- **Public-hosts-only by default** since September 2026: did:web and did:webvh resolution refuse loopback/private/link-local/metadata targets (one `with_host_policy(HostPolicy::AllowPrivate)` setting opts local stacks back in); a raw did:webvh log cache on the cache server stops hot DIDs hammering their host + +### Egress guard (`affinidi-net-guard`) — *new September 2026* +A leaf crate (no `affinidi-*` dependencies, so [[didwebvh-rs]] and external clients can use it) that vets attacker-influenceable URLs — a `did:web` host, a service endpoint, a redirect `Location` — against loopback, private, CGNAT, link-local and cloud-metadata space, *and* installs a DNS resolver that refuses any non-routable answer and pins the connection to the checked addresses (closing DNS rebinding). Documented in ADR 0006; now underneath the mediator's forwarding client. ### Messaging (`affinidi-messaging`) Secure messaging built on [[didcomm|DIDComm v2.1]] — and, since August 2026, DIDComm **v1** for interop with Aries/Credo-lineage wallets: - SDK, mediator/relay service, and terminal chat client - Authcrypt and anoncrypt packing; since 0.19.0 (July 2026) **unpack accepts only authenticated envelopes by default** (`authcrypt`, `authcrypt(sign)`, `anoncrypt(authcrypt)`) and enforces `from == skid` -- Message forwarding and routing; cross-mediator forwarding when the next hop is a DID -- Production features: circuit breakers, rate limiting, graceful shutdown, `explicit_allow` ACL mode gating authentication +- Message forwarding and routing; cross-mediator forwarding when the next hop is a DID — federation of independent mediators is documented in `docs/multi-mediator.md` (September 2026), and DIDComm v2 mediation is **DID-addressed with no keylist** (`docs/mediation-and-routing.md`; the keylist exists only for DIDComm v1) +- Production features: circuit breakers, per-IP *and* per-DID rate limiting (429s that name the refusing service), graceful shutdown, `explicit_allow` ACL mode gating authentication; since 0.25 every refused delivery answers one uniform `delivery.refused` so an unauthenticated sender cannot probe which ACL rule turned it away ### Reliable delivery (`affinidi-messaging-core`, `affinidi-messaging-delivery`) — *new July 2026* The transport-neutral contracts and the reliability layer above them. `affinidi-messaging-core` defines `MessagingProtocol` (packing) and the `MessageTransport` trait (truthful `send` → hop-acceptance receipt, re-falsifiable connection state, `inbound()` + `ack()` *after* durable handoff). `affinidi-messaging-delivery` adds a durable **outbox** (`Queued → Sent → Delivered | Unconfirmed | Failed`), a `MessagingService` front-end (`send(BestEffort|Guaranteed)`, thread-correlated `request`, `subscribe`), delivery evidence (outbox-drain, layer-receipt, protocol-reply), escalate-on-expiry, multi-transport and multi-identity operation, and a conformance suite. This is what the [[verifiable-trust-agent|VTA]], the VTC service, and [[openvtc]] now run their messaging on. @@ -34,19 +37,21 @@ The transport-neutral contracts and the reliability layer above them. `affinidi- Human-memorable handles for DIDs — `example.com/@alice`, or the community form `example.com/@` — implemented as a *shortcut layer*, not a DID method: the name URL redirects to a DID, the DID resolves normally, and the DID document **must claim the name back in `alsoKnownAs`** (typed in `affinidi-did-common` 0.4) before it is ever displayed. Exposed via `resolve_any()` in the resolver cache SDK, an opt-in `resolve-name` endpoint on the cache server (rate-limited by the new `affinidi-rate-limit` crate), and display-name shortcuts. See [[decentralized-identifiers]]. ### Trust Spanning Protocol (`affinidi-tsp`) -Implementation of the Trust over IP [[trust-spanning-protocol|TSP]] specification: -- HPKE-Auth encryption (RFC 9180), CESR binary encoding +Implementation of the Trust over IP [[trust-spanning-protocol|TSP]] specification — since 0.2.0 (September 2026) **specification Rev 3**, which shares no frame with Rev 2 (no compatibility mode, no negotiation): +- **HPKE-Base** encryption (RFC 9180 mode `0x00`; Rev 2 used HPKE-Auth) with sender authenticity carried by the ESSR signature alone; CESR binary encoding; version marker `YTSP-AAC` - Direct, Routed, and Nested message modes; mediator-integrated routing and federation -- **Graduated from experimental to supported in June 2026** and declared fully interoperable with the ToIP reference `tsp_sdk`; the mediator now serves TSP and DIDComm on the same endpoint, and clients prefer TSP when both ends support it +- **Relationships are enforced, not just recorded** (§7.2.2): an application message from a VID with no relationship is dropped by default; invites/accepts/cancels, the invite-race tiebreak, `Reply_Path`, `Referral_Field`, padding (`XPAD`) and upper-layer control (`XCTL`) are all implemented, plus durable relationship stores and a recovery runtime +- **Post-quantum** (ML-KEM-768 + X25519 hybrid KEM, ML-DSA-65 signatures) implemented behind a `pq` feature and verified against the spec's own vector — the VID model above it is not yet PQ-aware +- **Graduated from experimental to supported in June 2026** and interoperable with the ToIP reference `tsp_sdk` (0.10.0 for Rev 3; 14/14 harness runs); the mediator serves TSP and DIDComm on the same endpoint and clients prefer TSP when both ends support it. The **DIDComm↔TSP bridge was removed** in Rev 3 — re-signing in the middle is exactly what ESSR forbids — so both ends of a conversation must be on one protocol ### Cryptographic Primitives -- Ed25519, P-256, secp256k1 key support +- Ed25519, P-256, secp256k1 key support; post-quantum ML-DSA-44/65/87 (persistable as `-priv-seed` multikeys since September 2026) and SLH-DSA (memory-only — no registered private-key multicodec) - W3C Data Integrity proofs (EdDSA JCS 2022, EdDSA RDFC 2022) - Multibase/multicodec encoding - RDF canonicalization ### Credentials (`affinidi-vc`, `affinidi-sd-jwt`, `affinidi-bbs`, `affinidi-mdoc`, OpenID4VC crates) -W3C VC data model + Data Integrity suites (EdDSA JCS/RDFC 2022, BBS-2023, and since August 2026 `ecdsa-jcs-2019` for P-256 device keys); Selective Disclosure JWT (RFC 9901); BBS signatures / blind BBS / pseudonyms; **ISO mdoc** (CBOR codecs for `IssuerSigned` and `DeviceResponse`, August 2026) — the format the VTA now receives and presents over OID4VP; `affinidi-openid4vp` / `affinidi-openid4vci`; status lists and trust lists. +W3C VC data model + Data Integrity suites (EdDSA JCS/RDFC 2022, BBS-2023, and since August 2026 `ecdsa-jcs-2019` for P-256 device keys; ML-DSA suites behind a `ml-dsa` feature); Selective Disclosure JWT (RFC 9901); BBS signatures / blind BBS / pseudonyms; **ISO mdoc** (CBOR codecs for `IssuerSigned` and `DeviceResponse`, August 2026; on `coset` 0.4 since `affinidi-mdoc` 0.3.0) — the format the VTA now receives and presents over OID4VP; `affinidi-openid4vp` / `affinidi-openid4vci`; status lists and trust lists. ### Meeting Place (`affinidi-meeting-place`) Discovery and connection service using DIDs. @@ -63,6 +68,65 @@ The TDK is the Swiss Army knife that everything else depends on: The TDK is a multi-crate workspace; entries below name the affected crate. Direction is toward production readiness with stronger security guarantees and better modularity. Implementation is evolving quickly; treat low-level APIs as in flux. +The August–September 2026 cycle (81 commits, PRs #716–#821; ~55k insertions, most of them between Dogwood-R1 and Eucalyptus-RC-0) had one headline and three undercurrents. The headline: **TSP moved to specification Rev 3** — a clean wire break with post-quantum cryptography underneath and relationship gating on top. The undercurrents: a **security review campaign** (SEC-3896–3899 and SEC-4045, "Dogwood security recon") that produced an egress-guard crate, public-hosts-only DID resolution, and a run of mediator authorisation fixes; **multi-mediator federation** finally working in production (and documented); and a **dependency ladder** — `trust-tasks-rs` 0.9 → 0.21 in seven steps — that, because Trust-Task types cross the messaging SDK's public API, forced a minor bump on every messaging consumer each time (mediator 0.18 → **0.26**, messaging-sdk 0.19 → **0.26**, `affinidi-tdk` facade 0.8 → **0.16**, didcomm-service 0.3 → **0.11**). Three coordinated tags fell in the window ([[coordinated-releases]]): **`VTI-Dogwood`** (2026-08-30, tag-only, no GitHub Release — deliberately quiet, mostly dependency and did-method work), **`VTI-Dogwood-R1`** (09-01, a re-cut with four production fixes) and **`VTI-Eucalyptus-RC-0`** (09-17, in flight). New crates: `affinidi-net-guard`, `did-webs`, `did-ethr`, `did-pkh`, `did-jwk`. + +### Release snapshots — Dogwood, Dogwood-R1, Eucalyptus-RC-0 + +| Crate | Cypress (08-17) | VTI-Dogwood (08-30, #747) | VTI-Dogwood-R1 (09-01, #751) | VTI-Eucalyptus-RC-0 (09-17, #818) | HEAD (09-18, #821) | +|---|---|---|---|---|---| +| affinidi-messaging-mediator | 0.18.19 | 0.20.3 | 0.20.6 | 0.26.2 | 0.26.3 | +| affinidi-messaging-sdk | 0.19.8 | 0.21.0 | 0.21.1 | 0.26.7 | 0.26.7 | +| affinidi-messaging-didcomm-service | 0.3.26 | 0.5.0 | 0.5.1 | 0.11.0 | 0.11.0 | +| affinidi-tsp | 0.1.14 | 0.1.14 | 0.1.15 | 0.2.1 | 0.2.1 | +| affinidi-messaging-core / -delivery | 0.1.6 / 0.1.14 | 0.1.6 / 0.1.14 | 0.1.6 / 0.1.14 | 0.1.8 / 0.1.15 | 0.1.8 / 0.1.15 | +| affinidi-messaging-mediator-common / -config | 0.15.34 / 0.2.1 | 0.15.36 / 0.2.1 | 0.15.37 / 0.2.1 | 0.16.0 / 0.3.0 | 0.16.2 / 0.3.0 | +| affinidi-messaging-test-mediator | 0.2.51 | 0.4.0 | 0.4.3 | 0.9.1 | 0.9.1 | +| affinidi-messaging-didcomm-v1 | 0.2.0 | 0.2.1 | 0.2.1 | 0.2.1 | 0.2.1 | +| `affinidi-tdk` facade / tdk-common | 0.8.5 / 0.6.7 | 0.10.0 / 0.6.10 | 0.11.0 / 0.6.10 | 0.16.0 / 0.6.11 | 0.16.0 / 0.6.11 | +| did-resolver-cache-sdk / -server | 0.8.22 / 0.9.10 | 0.8.34 / 0.9.12 | 0.8.34 / 0.9.12 | 0.8.38 / 0.9.14 | 0.8.38 / 0.9.14 | +| did-scid / did-webs / did-web | 0.1.13 / — / 0.1.3 | 0.2.5 / 0.7.0 / 0.1.3 | 0.2.5 / 0.7.0 / 0.1.3 | 0.2.7 / 0.7.0 / 0.1.5 | same | +| affinidi-net-guard / affinidi-rate-limit | — / 0.1.0 | — / 0.1.0 | — / 0.1.0 | 0.1.0 / 0.1.2 | 0.1.0 / 0.1.2 | +| affinidi-did-authentication / did-common | 0.3.10 / 0.4.1 | 0.3.12 / 0.4.2 | 0.3.12 / 0.4.2 | 0.3.13 / 0.4.2 | same | +| affinidi-data-integrity / affinidi-mdoc | 0.7.10 / 0.2.7 | 0.7.10 / 0.2.7 | 0.7.10 / **0.3.0** | 0.7.12 / 0.3.1 | 0.7.13 / 0.3.1 | +| affinidi-secrets-resolver / crypto | 0.5.9 / 0.2.7 | 0.5.10 / 0.2.8 | 0.5.10 / 0.2.8 | 0.5.12 / 0.2.9 | same | +| consumed: trust-tasks-rs / vta-sdk / [[didwebvh-rs]] | 0.9 / 0.25 / 0.6 | 0.17 / 0.32 / 0.6 | 0.17 / 0.32 / 0.6 | 0.21 / 0.40 / 0.7 | same | + +`VTI-Dogwood-RC-1` (08-29) sits one commit before Dogwood (#746). Crate CHANGELOGs label most of these versions "Unreleased (x.y.z)" even though per-crate git tags and crates.io publishes exist for them — the coordinated tag, not the CHANGELOG, is the reference point. + +**Dogwood vs Dogwood-R1.** R1 is Dogwood plus four PRs (#748–#751, ~2,200 lines), three of them production federation bugs found *after* Dogwood was cut: with the default `RelayMode::Blind`, a mediator refused *every* message relayed by a peer as a session mismatch, so no cross-mediator DIDComm delivery completed (#748, mediator 0.20.4); the problem report the mediator sends when it abandons a forward went out as bare plaintext, which every authcrypt-only SDK client discards, so abandonment was silent to the sender (#749, 0.20.5); and TSP remote forwarding treated a next hop whose `TSPTransport` endpoint is a *mediator DID* as a URL and failed after five retries — now resolved one hop, mirroring the DIDComm fix in #705 (#750, 0.20.6 / tsp 0.1.15). The fourth, `affinidi-mdoc` 0.3.0 on `coset` 0.4 (#751, facade 0.11.0), unblocked VTI #1225 in `vta-vault`. + +### TSP Rev 3 — 2026-09-15 → 09-17 (#796, #803–#806, #810, #812, #814–#817) + +**Why.** The ToIP specification moved to Rev 3, and Rev 3 changed the crypto mode, the version byte, the long count-code prefix and every payload type code at once. There is no frame the two revisions can both classify, so `affinidi-tsp` **0.2.0** is a deliberate hard break: "nothing this crate packs can be unpacked by a Rev 2 peer, and nothing a Rev 2 peer packs can be unpacked here" (a Rev 2 frame is now reported as `TspError::RevisionMismatch` rather than a misleading crypto error; `docs/tsp/rev3-migration.html` is the migration guide). + +- **Wire**: HPKE-Auth → **HPKE-Base** (sender authenticity from the ESSR signature alone); version `YTSP-AAC` read as MAJOR.MINOR (the crate documents *disagreeing* with the spec's `ABA` MAJOR.MINOR.PATCH reading, raised upstream on spec PR #63 — interop does not depend on it); `--X#####` long codes; new payload codes `XSCS/XHOP/XRFI/XRFA/XRFD/XCTL/XPAD`; signed-only wrapper gone; 128-bit nonce; §7.2.1 SAID digests. The libsodium sealed box (§8.3) is implemented for *reading* legacy peers only. +- **Protocol behaviour, all new**: application messages **gated on a relationship** (`RelationshipPolicy::Gated` default, §7.2.2); invite-race tiebreak by digest (§7.2.3); three-case cancellation (§7.3); key-state re-resolution on failure and after silence with rate limiting (§7.4.2, `KeyStatePolicy`); `Reply_Path` (§7.2.4); `Referral_Field` (§7.2.5); fillable padding and `XPAD`; `XCTL`. Two latent bugs found on the way: long-form counts decoded to the wrong length, and long-framed TSP messages were not recognised as TSP by any ingress classifier. +- **Conformance**: the specification's own ten Appendix A vectors run as a test suite (checked against `tsp_sdk` 0.10.0's `rev3.json`), all passing including the post-quantum one; five defects in the published appendix were found and reported; the AAC vectors re-pack byte for byte (#810). `MAX_HOPS` raised 16 → 64 (#806). +- **Post-quantum** (`pq` feature, `docs/tsp/post-quantum.md`): hybrid `MLKEM768-X25519` KEM and ML-DSA-65 signatures, opening the spec's `direct-hpke-base-pq` vector end to end. Built only once four appendix defects (short values, an unexplained 32-byte ML-KEM key that turned out to be a seed, a naming clash with X-Wing, unrendered FIPS references) were resolved upstream — "a hybrid KEM ... would have shipped validated by nothing but agreement with themselves". Status: cryptography verified, VID model not yet PQ-aware, so nothing above the crate uses it. +- **Up the stack**: messaging-sdk **0.25.0** — `unpack_message` returns `InboundTsp` (application vs control vs `Padding` vs `UpperLayerControl`), relationship state enforced, key-state freshness on every unpack, `send_padding` / `send_generic_control`; messaging-core **0.1.8** — an `Inbound` says whether it is traffic or a request about the relationship (#812, breaking for constructors); didcomm-service **0.9.0** handles TSP control messages (it had no relationship lifecycle, which under gating would have made it inert) and **0.11.0** adds `ListenerConfig::with_relationship_store` + `tsp_ensure_relationship` (#817); mediator **0.25.0** — a single uniform `delivery.refused` replaces five ACL-revealing problem codes, the **DIDComm↔TSP bridge is removed**, and a relayed endpoint-to-endpoint VID is no longer written to the durable forward queue (§5.3.3). +- **Relationship recovery runtime** (tsp 0.2.1, sdk 0.26.5–0.26.7, #814–#816): reconcile transition, `PersistentRelationshipStore`, recovery-aware send (`SendReadiness`), capped-exponential backoff, 7-day idle eviction, single-flight `RecoveryCoordinator`, inbound-invite rate limiter, §7.2.2 drop counter. The consumer side lives in [[affinidi-webvh-service|did-hosting-service]] (#193/#194) and the VTA/VTC (VTI #1525). + +### Security review campaign — SEC-3896–3899, SEC-4045 — September 2026 + +- **`affinidi-net-guard`** (#788, ADR 0006): the `did:web` SSRF fix from #753 (block non-routable hosts, cap body at 1 MiB) generalised into a leaf crate — URL vetting *and* a guarded DNS resolver, because `reqwest` never consults a custom resolver for an IP-literal host and a proxy bypasses the resolver entirely. Applied to the mediator's forwarding HTTP/WS client (#819, mediator-common 0.16.1: fail-closed resolver, no redirects, no proxy, https/wss + globally-routable only), with a TLS-validation hard gate in release builds and a WS DNS-rebinding guard (#820, 0.16.2). +- **Public-hosts-only DID resolution**: did:web in `affinidi-did-web` 0.1.4 / cache-sdk 0.8.35; did:webvh via [[didwebvh-rs]] **0.7** in cache-sdk 0.8.37 / cache-server 0.9.13 / did-scid 0.2.7 (#789). One `with_host_policy(HostPolicy::AllowPrivate)` opts local stacks back in. +- **Mediator TSP authorisation parity** (#756, #762, #765, #819): the TSP ingress trusted the envelope's *claimed* sender — an authenticated client could borrow any allow-listed VID (#754; 0.20.7 binds the outer sender to the session DID, the TSP twin of the 0.15.5 DIDComm fix); TSP direct delivery now honours `local_direct_delivery_allowed` (0.20.9, a behaviour change); the peer-mediator allowlist applies to TSP relay hops, with a *stronger* guarantee than DIDComm because every TSP hop is authenticated by construction (0.20.10); TSP routed relay requires `SEND_FORWARDED` and `/readyz` no longer leaks backend detail (0.26.3). +- **Rate limits that exist**: `did_rate_limit_per_second` was parsed, announced and never called — now charged once per authenticated request (#808, 0.26.2); a 429 names the mediator (`x-rate-limit-source`) and is typed all the way down — `HttpStatusError` in core 0.1.7, `ATMError::HttpStatus`, `NetworkFetchError` in the resolver (a rate-limited DID host no longer looks like an invalid DID, and N concurrent resolutions of a failing DID no longer make N fetches) (#801, #805, #807, #816). +- **Supply chain**: `Cargo.lock` committed and `--locked` passed everywhere a build is emitted, including the wizard-generated Dockerfile (#793; the trigger was an `aws-smithy-types` 1.7 minor that broke lockless builds, #791); fuzz CI pinned and egress-blocked (#753); did:cheqd resolution retired and `did-resolver-cheqd` dropped, clearing eight advisories (#763); did:ethr / did:pkh / did:jwk re-implemented in-tree so no `ssi-*` crate is compiled anywhere (#719); `jsonwebtoken` made private and moved to 11 (#772); keyring 4 via `keyring-core` with the Linux keyutils store kept deliberately (#776); RustCrypto `digest` 0.11 generation (#777). +- **Post-quantum hygiene**: ML-DSA secrets could not be persisted — `secret_material` was written as an empty string, so a saved store lost the key on restart (#797, secrets-resolver 0.5.12); the seven PQ multicodec code points pinned against a dated `table.csv` commit, with the note that `-priv-seed` is what lets an ML-DSA key be re-derived from a BIP-32 chain (#798); the data-integrity ML-DSA tests had never compiled (#821, 0.7.13). + +### Multi-mediator federation — 2026-08-31 → 09-08 (#748–#750, #759, #781–#783) + +The Dogwood-R1 fixes above were the symptom; the cause was that federation had never been written down. `docs/multi-mediator.md` (#781, mediator 0.22.1) now documents the two hop shapes (a double forward needs `RECEIVE_FORWARDED`; `send_to`'s single forward arrives as *direct delivery* and needs `local_direct_delivery_allowed`), the four accounts one cross-mediator delivery consults — including the non-obvious one, that the *peer mediator's* DID needs an account with `RECEIVE_FORWARDED` — blind vs rewrap relay, and a symptom-to-cause table; "the shipped default grants neither forwarded bit, so it is not a federation configuration". The gaps it exposed were then closed: an inter-mediator relay over **WebSocket with a per-frame `RelayAck`** (#782, 0.22.2 — never registered with the streaming task, gated on `SEND_MESSAGES`, bounded one-hour lifetime); **management Trust Tasks answered over TSP** so a TSP-only client can set its own ACL (#783, 0.22.3 — the dispatch split into a core plus a wrapper per transport); and `docs/mediation-and-routing.md` (#759, 0.20.8) stating the v2 addressing contract — DID-addressed, no keylist, `coordinate-mediation` deliberately not advertised — in answer to a downstream transport binding's question. + +### DID methods — did:webs, did:scid:ke, in-tree ethr/pkh/jwk — 2026-08-22/23 (#719, #723, #734–#741) + +**`did-webs`** (~new crate, published at 0.7.0) resolves `did:webs` — did:web discovery with **KERI-verified key state**: the document is *derived* from the verified `keri.cesr` key event log, never copied from the published `did.json`, which is cross-checked and fails resolution on disagreement. Verified: every event's SAID, digest chain, controller signatures, pre-rotation commitments, delegation seals (bounded, cycle-checked), witness receipts when a KEL declares witnesses (#735), and *designated aliases* — `alsoKnownAs` counts only if the AID issued an anchored credential saying so (#734, #741). A `create` feature adds create/update (#740). **`did-scid` 0.2.x** adds `did:scid:ke:1` (KERI AIDs via did:webs, #736 — flagged as a *proposed* registry entry, and note the SCID sits first for webvh but the AID sits last for webs) and fixes `did:scid:ke` being unreachable through resolve (#739). Hosting-side consumer: [[affinidi-webvh-service|did-hosting-service]] #169. + +### Dependency ladder + facade — throughout + +`trust-tasks-rs` 0.11 (#717) → 0.12 (#743; `ConsumeChecks` required, `ping` gains freshness bounds) → 0.17 (#744; generated types `#[non_exhaustive]`, builders mandatory — the mediator now *refuses* an unknown account role rather than storing a privilege it cannot reason about) → 0.18 (#769/#771) → 0.19 (#784) → 0.20 (#786) → 0.21 (#799). Each step is a minor on the facade (0.9, 0.10, 0.12, 0.14, 0.16) because "two `trust-tasks-rs` versions in one graph fail to compile rather than warn". The mirror problem — `vta-sdk` pinning *older* TDK crates so `[patch.crates-io]` silently stopped applying and the lockfile carried two `affinidi-tdk`, two SDKs, two `didwebvh-rs` — was unwound twice (#792 to vta-sdk 0.38, #811 to 0.40): the frozen registry SDK had compiled against the *patched* messaging-core, "freezing this workspace's API against the registry" and blocking the delivery layer from carrying a TSP message kind. Dogwood also carried mediator-setup fixes for a VTA reachable only over DIDComm/TSP (no REST URL, #745/#746), CORS refusals that name the origin (#747), a websocket close reason so a refused duplicate stops reading as a network fault (#718), Credo 0.7.0 fixtures for the DIDComm v1 harness (#794), and a cache-server that actually serves `did:jwk` (0.9.11) and caches raw did:webvh logs (0.9.14, #813). + The July–August 2026 cycle (103 commits, PRs #589–#716) shifted from *transports* to *reliability and names*: a new **reliable messaging delivery layer**, the **agent names** shortcut layer, a run of security-by-default breaking changes (authcrypt enforced, ACL gating authentication), and **DIDComm v1** for Aries/Credo interop — capped by the coordinated **`Cypress`** release ([[coordinated-releases]], tag at #715, 2026-08-17). Four new crates: `affinidi-messaging-delivery`, `agent-names`, `affinidi-rate-limit`, `affinidi-messaging-didcomm-v1`. ### Cypress snapshot — 2026-08-17 diff --git a/content/entities/affinidi-webvh-service.md b/content/entities/affinidi-webvh-service.md index 2a56437..a250815 100644 --- a/content/entities/affinidi-webvh-service.md +++ b/content/entities/affinidi-webvh-service.md @@ -1,8 +1,8 @@ --- title: "did-hosting-service (formerly Affinidi WebVH Service)" type: entity -tags: [affinidi, webvh, did-hosting, did-web, service, secondary, multi-method, multi-domain, tsp, agent-names, cypress] -date-updated: 2026-08-19 +tags: [affinidi, webvh, did-hosting, did-web, did-webs, service, secondary, multi-method, multi-domain, tsp, agent-names, cypress, dogwood, eucalyptus] +date-updated: 2026-09-18 repo: https://github.com/affinidi/affinidi-webvh-service --- @@ -10,34 +10,34 @@ repo: https://github.com/affinidi/affinidi-webvh-service *Repo: [github.com/affinidi/affinidi-webvh-service](https://github.com/affinidi/affinidi-webvh-service)* -Production infrastructure for hosting, resolving, and managing [[did-webvh|did:webvh]] (and, since v0.7.0, `did:web`) identifiers at scale. It's the operational backbone that makes self-hosted DIDs practical — handling the hosting, witnessing, and monitoring that individual users shouldn't have to manage themselves. +Production infrastructure for hosting, resolving, and managing [[did-webvh|did:webvh]] (since v0.7.0 also `did:web`, and since August 2026 `did:webs` — KERI-anchored — behind an off-by-default `method-webs` feature) identifiers at scale. It's the operational backbone that makes self-hosted DIDs practical — handling the hosting, witnessing, and monitoring that individual users shouldn't have to manage themselves. The repository was **renamed from `affinidi-webvh-service` to `did-hosting-service`** in v0.7.0 (May 2026). The rename reflects two architectural shifts: the service is no longer single-method (it now hosts both `did:webvh` and `did:web` by default, with a `DidMethod` trait abstraction and compile-time feature gating), and the binaries are now named for the **capability** they expose rather than a specific underlying method (`did-host-http`, `did-host-didcomm`, `did-host-http-didcomm`). ## Components -The service is a Rust workspace. Since the v0.7.0 rename the crates are named for the *capability* they expose, and since 0.8.0 (July 2026) they are **versioned per crate** — so a snapshot lists several numbers. Versions below are those in the coordinated **`Cypress`** release ([[coordinated-releases]], 2026-08-17): +The service is a Rust workspace. Since the v0.7.0 rename the crates are named for the *capability* they expose, and since 0.8.0 (July 2026) they are **versioned per crate** — so a snapshot lists several numbers. The versions below were set at the coordinated **`Cypress`** release ([[coordinated-releases]], 2026-08-17) and are **unchanged at `VTI-Dogwood` (08-30), `VTI-Dogwood-R1` (same commit) and `VTI-Eucalyptus-RC-0` (09-17)** — a month of work accumulates under "Unreleased" in the CHANGELOG, and the coordinated tags are the only reference points: -| Crate | Port | Role | Cypress | +| Crate | Port | Role | Cypress … Eucalyptus-RC-0 | |-------|------|------|---------| -| **did-hosting-server** | 8530 | Core DID hosting — create, upload, resolve, delete DIDs; serves `did.jsonl` and `/@name` agent-name redirects | 0.8.3 | -| **webvh-witness** | 8531 | Generate cryptographic witness proofs for DID integrity (DIDComm-only) | 0.8.3 | -| **did-hosting-control** | 8532 | Management UI backend, service registry, reverse proxy, passkey auth, ACL, agent-name registry (source of record) | 0.8.8 | -| **webvh-watcher** | 8533 | Read-only DID mirror for redundancy (receives pushed updates) | 0.8.3 | +| **did-hosting-server** | 8530 | Core DID hosting — create, upload, resolve, delete DIDs; serves `did.jsonl` (webvh), `did.json` (web), `keri.cesr` + derived `did.json` (webs) and `/@name` agent-name redirects | 0.8.3 | +| **webvh-witness** | 8531 | Generate cryptographic witness proofs for DID integrity. Proof and list operations are **REST-only** since August 2026 (#175); the DIDComm listener remains only for mediator presence and identity rotation | 0.8.3 | +| **did-hosting-control** | 8532 | Management UI backend, service registry, reverse proxy (fail-closed on an empty allowlist since #196), passkey auth, ACL, agent-name registry (source of record), TSP relationship store | 0.8.8 | +| **webvh-watcher** | 8533 | Read-only DID mirror for redundancy (receives pushed updates; verifies the proof chain on push since #199) | 0.8.3 | | **did-hosting-daemon** | 8534 | All-in-one binary (server + witness + watcher + control) for simple deployments | 0.8.3 | -| **did-hosting-client** | — | Companion client crate for talking to a hosting node (DID management, agent names) | 0.1.2 | -| **webvh-ui** | — | Web UI bundle (packaged for cargo publish since #157) | 1.1.0 | -| **did-hosting-common** | — | Shared library: clients, DID operations, auth, storage, config, trust-task pipeline | 0.8.6 | +| **did-hosting-client** | — | Companion client crate for talking to a hosting node (DID management, agent names; typed `RateLimited` errors) | 0.1.2 | +| **webvh-ui** | — | Web UI bundle (packaged for cargo publish since #157; Node ≥ 24.3 to build since #182) | 1.1.0 | +| **did-hosting-common** | — | Shared library: clients, DID operations, auth, storage, config, trust-task pipeline and the one transport-agnostic router | 0.8.6 | -Every node is a **three-transport, document-driven** endpoint: HTTPS (`POST /api/trust-tasks`), [[didcomm|DIDComm v2]], and [[trust-spanning-protocol|TSP]] all deliver the same Trust Task documents to one dispatch core, and a node's DID document is the authority for which transports it offers. +Every node is a **three-transport, document-driven** endpoint: HTTPS (`POST /api/trust-tasks`), [[didcomm|DIDComm v2]], and [[trust-spanning-protocol|TSP]] all deliver the same Trust Task documents to one dispatch core (literally one router since #183 — HTTPS had quietly served a subset), and a node's DID document is the authority for which transports it offers. Since #171 that includes **authentication**: the `auth/*` family is dispatched as Trust Tasks on every binding, so a wallet can log in over TSP, and the proof in the document — not the transport — is what establishes the signer. TSP is the **Rev 3** line (`affinidi-tsp` 0.2) since Eucalyptus-RC-0, with relationships persisted per node so a restart does not silently drop the control plane's pushes. ## How It Works 1. A user creates a did:webvh via the [[verifiable-trust-agent|VTA]] 2. The VTA uploads the DID document and history log to a webvh-server 3. The server serves the `did.jsonl` at the DID's well-known URL -4. Witnesses attest to the integrity of DID updates via DIDComm protocol -5. Watchers mirror the DID for redundancy +4. Witnesses attest to the integrity of DID updates (proof requests over the witness's REST API, authenticated by DID challenge–response) +5. Watchers mirror the DID for redundancy, verifying the pushed proof chain 6. The control plane manages service registration and access control ## VTA Integration @@ -51,7 +51,34 @@ The cold-start bootstrap flow (`import-secrets` CLI) can bring up an entire envi ## Recent Development -After hardening cross-service trust paths in v0.6.0 and pivoting to multi-domain / multi-method in v0.7.0, the July–August 2026 cycle (101 commits, PRs #59–#167) shipped **0.8.0** — "transport as a first-class negotiable property of every node" — then converged the service hard on the ToIP **Trust Tasks** registry as its wire authority, added **agent names** as a new product surface, and landed in the coordinated **`Cypress`** release ([[coordinated-releases]]). Note: the wiki previously reported that 0.8.0 would *drop* the legacy `/api/acl` REST surface; it did not — at HEAD it is still served with `Deprecation`/`Sunset: 2026-12-01` headers pointing at `/api/trust-tasks`. +The August–September 2026 cycle (33 commits, PRs #169–#200) shipped **no version bump at all** — every crate is still at its Cypress number — yet changed a great deal underneath. Its shape is the ecosystem's: a quiet **`VTI-Dogwood`** (2026-08-30, tag-only; `VTI-Dogwood-R1` points at the *same* commit here, #178, unlike the TDK where R1 is a re-cut) that mostly absorbed the `trust-tasks-rs` / `vta-sdk` ladder, added **did:webs hosting**, and finished the migration of *authentication* onto Trust Tasks; then a run-up to **`VTI-Eucalyptus-RC-0`** (09-17, #194) that fixed a control-plane lockout, unified the transport router, and moved to **TSP Rev 3** with persisted relationships; and, after the RC was cut, a six-PR **security wave** (SEC-4045 R2/W6, #195–#200) closing IDOR, quota, replay, SSRF and authorisation gaps. The legacy `/api/acl` REST surface is *still* served with `Deprecation`/`Sunset: Mon, 01 Dec 2026` headers — and gained a last-authority guard in #195 — so its removal remains scheduled, not done. Nothing in this repository touched Kubernetes, Vault or VTA-Farm deployment in the window; that work lives in the VTI side ([[vtafarm]], [[vtafarm-k8s]]). + +### Post-RC security wave — SEC-4045 R2 / W6 — 2026-09-17/18 (#195–#200) + +**Why.** A structured security review across the VTI estate (the same SEC-4045 series that produced the [[affinidi-tdk|TDK]]'s `affinidi-net-guard` and the mediator's TSP authorisation fixes) reached did-hosting after the RC was cut. The findings are the classic multi-tenant-service set, and each fix names its CWE. + +- **Control plane** (#195): per-DID stats/timeseries scoped to the owner and `?domain=` aggregates to the caller's `DomainScope` (W2, IDOR); `max_did_count` / `max_total_size` per account actually enforced on create/register/publish (W3); the **replay cache now gates the TSP and HTTPS trust-task transports** too, not just DIDComm (W4); registry URL validation default-denies non-routable/metadata literals via net-guard's `is_globally_routable` even with an empty allowlist (W6); last-authority guard on the legacy `PUT /api/acl/{did}` (W5); `DomainScope` re-checked at time of use on delete/change-owner/disable/rollback (W7). **Edge**: witness uploads count against quota (W8); the did.jsonl proof chain is verified on the edge publish path, parity with control (W9); server-wide stats need Admin (W14). +- **Admin proxy fails closed** (#196/#197): with an empty registry allowlist the proxy would forward an Admin's bearer token to *any* public registered URL — so a Service-role DID could register an attacker host and harvest it. It now refuses on an empty allowlist and otherwise requires the host to be listed; documented as the fail-closed behaviour operators must configure around. +- **AuthZ parity** (#198): edge and witness gain the last-remaining-admin demotion guard the control plane got in W5; `GET /api/config` (backend topology, DIDs, data dir) and server-wide stats/timeseries become admin-only; the UI hides those panels for non-admins. +- **Robustness** (#199): all outbound clients (watcher, witness, control, webvh) share a hardened builder — request + connect timeouts, redirects refused — so a hung peer cannot pin a task and a 3xx cannot bounce a bearer token; `MAX_LOG_ENTRIES` cap before per-entry proof verification; proxy response body capped at 10 MB; `list_dids` page limit and `did_deltas` per stats payload capped; **the watcher verifies the proof chain on sync push**, not just structure. +- **Freshness** (#200): `unpack_signed` only enforced the 5-minute window when `created_time` was present, and the replay cache TTL equals that window — so a signed DIDComm envelope with no `created_time` never went stale and could be replayed to re-trigger a state-changing DID op after its cache entry aged out. `created_time` (inside the JWS-signed payload) is now required; every first-party sender, including the VTA's webvh client, already sets it. + +### Eucalyptus-RC-0 run-up — TSP Rev 3, one router, no more lockouts — 2026-09-08 → 09-17 (#179–#194) + +- **TSP Rev 3** (#191–#194; `affinidi-tsp` 0.1 → 0.2, TDK 0.14 → 0.16, didcomm-service 0.8 → 0.11, vta-sdk 0.38 → 0.41, trust-tasks 0.20 → 0.21). No workspace source change was needed for the wire break — "this service speaks trust-tasks documents, not TSP envelope internals" — but Rev 3's §7.2.2 gating exposed two application-level gaps. A receiver drops any application frame from a VID it holds no relationship with, and completing the handshake is the *application's* job: neither TSP listener did it, so an ephemeral edge that lost its relationship half logged "no relationship … discarded" and dropped the control plane's sync/health pushes indefinitely. #193 adds the **answering arm** (`answer_inbound_control`, shared by `WebvhTspHandler` and `ServerTspHandler`; invites accepted from any framework-authenticated sender, with authorisation left to the task layer because §3.6 lets a peer pack its first application message *with* the invite). #194 adds **persistence + re-invite**: a backend-agnostic `RelationshipKv` over any `StorageBackend` (new `KS_TSP_RELATIONSHIPS` keyspace) injected into both listeners, and the edge calling `tsp_ensure_relationship` with its control plane on every (re)connect. Mirrors the VTA/VTC reference (VTI #1525) so the fleet answers consistently. See `docs/tsp-transport.md`. +- **One router for every transport** (#183): the HTTPS `/api/trust-tasks` route re-implemented dispatch and asked only one of the five questions the shared router asks — so the typed DID-management family, the auth family and the infra family all fell through to the legacy `MSG_*` bridge and came back "unknown op" (an agent-name update over HTTPS being the concrete casualty). Root cause: the router flattened everything to `Option`, losing the framework reject code HTTPS needs for its status line. A `RoutedReply` enum (`Framework` / `Document` / `Suppressed`) fixes the cause; the HTTPS route no longer builds its own dispatcher. +- **Rate-limit refusals are 429s that say who refused** (#189): every limiter (`auth-challenge-per-ip`, `-pending-per-did`, `-pending-global`) answered `400`, indistinguishable from a malformed request. Now `429` + `x-rate-limit-source: did-host` + `Retry-After` + a JSON body — the contract the VTA, VTC and mediator emit and `vta_sdk::rate_limit` reads; `did-hosting-client` gains `ClientError::RateLimited`. +- **Abandoned auth challenges no longer lock out the control plane** (#190, SEC-4045 W1): the pending-challenge tracker released a slot only on successful authentication, so ten abandoned challenges locked a DID out of `/api/auth/challenge` for good and ~10,000 across any DIDs locked out every login — an unauthenticated DoS whose `Retry-After` promised a retry that could never succeed. Challenges now expire with `challenge_ttl`, the tracker is seeded from the session store at boot, and `auth/challenge/0.1` over DIDComm/TSP/HTTPS is held to the same caps (it had bypassed them). Caps are now operator-configurable. +- **Dependency ladder** (#181, #182, #185): trust-tasks 0.17 → 0.18 → 0.19 (held there deliberately until VTI published a vta-sdk on ^0.20) → 0.20; vta-sdk 0.32 → 0.34 → 0.35 → 0.38; Node 20 → **24.3** (vitest 5). **`--locked` everywhere** (#187), because `aws-smithy-types` 1.7.0 broke `aws-smithy-json` in a *minor* release and `cargo install` ignores `Cargo.lock` by default (#186, #188); patched rustls for RUSTSEC-2026-0285 (#184). Fuzz and upstream-drift CI pinned and egress-blocked (#179, SEC-3896/97/99). + +### Dogwood — did:webs hosting, auth as Trust Tasks, wallet-chosen identity — 2026-08-24 → 08-30 (#169–#178) + +- **`did:webs` hosting** (#169, `docs/did-webs-hosting.md`): registration and publish on the control plane, control→edge sync, and resolution of both artifacts, behind `method-webs` (off by default on every binary — it pulls the KERI stack). Verification is the point: `affinidi-did-webs` (without its `create` feature — the service never holds KERI keys) checks every event's SAID, the digest chain, signatures, pre-rotation, delegation seals, witness receipts and the designated-aliases attestation `alsoKnownAs` derives from; two rules are the service's own — a stream may only be published to the slot whose final segment is the AID it establishes, and an update may not rewind or fork the hosted log. Two deliberate departures from `multi-method-hosting-spec.md`: the CESR stream is stored and `did.json` is **derived on every read** (a stored copy "could only ever be right or stale — and stale here means serving a document from before a key rotation"), and a webs slot's trailing mixed-case AID is exempted from the lowercase path grammar. `POST /api/dids/register` accepts `method: "webs"` with the `keri.cesr` stream. Edges verify a synced webs log themselves, unlike webvh (structural only, for the stated reason that an older [[didwebvh-rs]] on the edge would reject logs the control plane accepted). +- **Authentication is a Trust Task on every binding** (#171–#175): auth had three shapes — canonical challenge/authenticate over HTTPS, a bespoke `MSG_AUTHENTICATE` over DIDComm where the authcrypt sender *was* the auth, and nothing over TSP. `auth/*` now has one identity on every wire, and the strict rule is chosen: the *document proof* establishes the signer, so the challenge is checked on every binding rather than being decorative on two of three. The legacy `affinidi.com/webvh/1.0/authenticate` URI was retired from REST (#172) — the dual-accept had already cost a silent browser-wallet login failure — and `did-hosting-client`'s refresh, which had been typed as an *authenticate-response* that no acceptor ever matched, works for the first time (#173). The witness's DIDComm `authenticate` moved to the canonical URI (#174) and then its whole DIDComm protocol mirror (`witness/proof-request`, `witness/list-request`) was removed as implemented-but-unreached (#175): the witness is a REST API; the DIDComm listener stays only for mediator presence and identity-rotation draining. +- **Wallet chooses the identity** (#177/#178, needs [[vta-browser-plugin]] #145): proxy sign-in used to enumerate every vault entry pinned to the RP — a disclosure of the operator's vault, with its own consent prompt, to find one entry. `walletProfile({ target })` resolves the entry bound to this origin (or asks once and remembers) and returns only the persona DID; the flow visualisation is unchanged. +- **trust-tasks 0.9 → 0.17, TDK 0.8 → 0.10, vta-sdk 0.25 → 0.31/0.32, didcomm-service 0.3 → 0.5** (#170, #176): generated payload types are `#[non_exhaustive]` (builders everywhere) and `consume_inbound` takes a required `ConsumeChecks`. The service passes `ConsumeChecks::not_consequential()`, which the CHANGELOG flags as **not the right long-term answer** for `acl/grant` / `acl/revoke` / `acl/change-role` — a mediator redelivery of one grant would execute twice; a real `ReplayGuard` needs storage and a decision about which processes share a VID. (The post-RC W4 fix above adds a replay-cache gate on the TSP/HTTPS transports; whether that fully retires this caveat is not stated.) `firestore` 0.53 forced the store backend onto the fluent builders. + +After hardening cross-service trust paths in v0.6.0 and pivoting to multi-domain / multi-method in v0.7.0, the July–August 2026 cycle (101 commits, PRs #59–#167) shipped **0.8.0** — "transport as a first-class negotiable property of every node" — then converged the service hard on the ToIP **Trust Tasks** registry as its wire authority, added **agent names** as a new product surface, and landed in the coordinated **`Cypress`** release ([[coordinated-releases]]). Note: the wiki previously reported that 0.8.0 would *drop* the legacy `/api/acl` REST surface; it did not — it is still served with `Deprecation`/`Sunset: 2026-12-01` headers pointing at `/api/trust-tasks`. ### Cypress snapshot + Trust-Tasks 0.9 — 2026-08-17 (#167) @@ -73,7 +100,7 @@ The big new feature of the cycle, and the hosting-side half of the ecosystem's * ### TSP decoupled from DIDComm — 2026-07-07 → 07-10 (#59–#71) -Follow-through on #58: DID-management ops accepted as trust-task documents over HTTPS too (`POST /api/trust-tasks` via `bridge_did_management`, #60; step-up over TSP declared a deliberate non-goal — no session to bind); a typed `did-hosting/did/*/1.0` protocol with eight ops — check-name, info, list, delete, publish, register, change-owner, witness-publish — carrying the `didLog` the upstream record-centric spec had no room for (#61); **outbound** control→server sync and domain pushes over TSP when the target advertises `TSPTransport` (#62/#63), closing the "no outbound Trust-Task sender" gap noted for #58; **TSP fully decoupled from DIDComm** — `FEATURES_TSP`, `Protocols` BOTH / TSP_ONLY / DIDCOMM_ONLY, a 3-way `TransportSelection` wizard/recipe field, TSP→DIDComm send fallback, DID docs advertising only the selected transports; the witness stays DIDComm-only (#64); TSP-only nodes mint the `did-host-http-tsp` VTA template (#65); DID-document services shown as badges across the controller UI (#67–#69); server registration and health as transport-agnostic trust tasks with a `trust_task_capable` flag for rolling upgrades (#70); observed control-link transport recorded per service instance ("↓in/↑out", #71). +Follow-through on #58: DID-management ops accepted as trust-task documents over HTTPS too (`POST /api/trust-tasks` via `bridge_did_management`, #60; step-up over TSP declared a deliberate non-goal — no session to bind); a typed `did-hosting/did/*/1.0` protocol with eight ops — check-name, info, list, delete, publish, register, change-owner, witness-publish — carrying the `didLog` the upstream record-centric spec had no room for (#61); **outbound** control→server sync and domain pushes over TSP when the target advertises `TSPTransport` (#62/#63), closing the "no outbound Trust-Task sender" gap noted for #58; **TSP fully decoupled from DIDComm** — `FEATURES_TSP`, `Protocols` BOTH / TSP_ONLY / DIDCOMM_ONLY, a 3-way `TransportSelection` wizard/recipe field, TSP→DIDComm send fallback, DID docs advertising only the selected transports; the witness stays DIDComm-only (#64 — later reversed: #175 in August removed the witness's DIDComm protocol mirror, leaving its REST API as the only proof surface); TSP-only nodes mint the `did-host-http-tsp` VTA template (#65); DID-document services shown as badges across the controller UI (#67–#69); server registration and health as transport-agnostic trust tasks with a `trust_task_capable` flag for rolling upgrades (#70); observed control-link transport recorded per service instance ("↓in/↑out", #71). ### TSP transport alongside DIDComm — 2026-07-07 (#58) — "everything is a trust task" diff --git a/content/entities/didwebvh-rs.md b/content/entities/didwebvh-rs.md index 9f7cca2..8d62d9f 100644 --- a/content/entities/didwebvh-rs.md +++ b/content/entities/didwebvh-rs.md @@ -2,7 +2,7 @@ title: "didwebvh-rs — did:webvh Rust Implementation" type: entity tags: [didwebvh, did, library, dif, secondary] -date-updated: 2026-08-19 +date-updated: 2026-09-18 repo: https://github.com/decentralized-identity/didwebvh-rs --- @@ -10,7 +10,7 @@ repo: https://github.com/decentralized-identity/didwebvh-rs *Repo: [github.com/decentralized-identity/didwebvh-rs](https://github.com/decentralized-identity/didwebvh-rs)* -A Rust library providing the reference implementation of the [[did-webvh|did:webvh]] DID method, conforming to the v1.0 specification from the Decentralized Identity Foundation (DIF). Currently at version 0.6.0 (July 2026). +A Rust library providing the reference implementation of the [[did-webvh|did:webvh]] DID method, conforming to the v1.0 specification from the Decentralized Identity Foundation (DIF). Currently at version **0.7.0** (September 2026), which made resolution *public-hosts-only by default* — a breaking change for local stacks. ## What It Provides @@ -28,7 +28,8 @@ The full DID lifecycle for did:webvh: Key design features: - **Pluggable signing** — a `Signer` trait lets callers provide their own signing backend (HSM, KMS, cloud) so private keys never enter the library -- **WASM-friendly** — resolution-only builds work in WebAssembly environments +- **Host policy on resolution** (0.7.0) — because the DID itself names the host to fetch from, `resolve()` refuses non-public hosts by default (`HostPolicy::PublicOnly`): special-use names (`localhost`, `*.local`, `*.internal`, `home.arpa`, single-label) are blocked before any request, and on native targets the default client refuses any name whose DNS answers include a loopback/private/link-local/CGNAT/metadata address, pins the connection to the checked addresses, follows no redirects and ignores system proxies. `HostPolicy::AllowPrivate` opts back in for local testing; `ResolveOptions::with_http_client()` lets a caller supply its own client (and take over the connect-time checks) +- **WASM-friendly** — resolution-only builds work in WebAssembly environments (only the name checks of the host policy apply there — DNS is not observable in a browser) - **Feature flags** — TLS backend selection (`rustls`, `native-tls`), optional `ssi` crate integration, CLI flows - **Embeddable CLI** — interactive terminal flows for third-party applications to integrate did:webvh operations @@ -38,7 +39,23 @@ This is a foundational building block. The [[affinidi-tdk|Affinidi TDK]] uses it ## Recent Development -The library is in maintenance-and-hardening mode: after the security-audit and spec-compliance releases of May–June and the June fuzzing infrastructure, July 2026 brought a parse-time spec fix (0.5.7) and a pre-release audit sweep that was promoted to **0.6.0** because it carries small breaking API changes. Activity is low (two PRs since July 6) — the crate is stable, and its downstream consumers (the [[affinidi-tdk|TDK]], [[affinidi-webvh-service|did-hosting-service]], the [[verifiable-trust-agent|VTA]], [[openvtc]]) all moved onto 0.6 within days. +The library is in maintenance-and-hardening mode: after the security-audit and spec-compliance releases of May–June and the June fuzzing infrastructure, July 2026 brought a parse-time spec fix (0.5.7) and a pre-release audit sweep promoted to **0.6.0** for its small breaking API changes. The August–September 2026 window (four PRs, #51–#54) added a spec-conformance fix (**0.6.1**) and one genuinely breaking security release (**0.7.0**, host policy) — the latter driven by the ecosystem-wide SSRF review (SEC-4045) that also produced the TDK's `affinidi-net-guard` crate and ADR 0006, whose stated design constraint is that the guard must be consumable *by this crate* (hence no `affinidi-*` dependencies). Consumers on 0.7 as of 2026-09-18: the [[affinidi-tdk|TDK]] resolver (`cache-sdk` 0.8.37, `cache-server` 0.9.13, `did-scid` 0.2.7 — #789, 09-12) and `vta-sdk` ≥ 0.38; [[affinidi-webvh-service|did-hosting-service]] still declares `didwebvh-rs = "0.6"` directly (its lockfile carries both 0.6.1 and 0.7.0 via the VTA SDK) — its edge servers deliberately re-verify synced logs only structurally because "an edge re-running it would reject logs an older didwebvh-rs accepted". + +Tag housekeeping worth knowing: the `v0.6.0` git tag was only pushed on 2026-08-28 (the commit is from 07-19), and `v0.6.1` / `v0.7.0` were both tagged on 09-11. + +### v0.7.0 — 2026-09-11 — resolution host policy + injectable HTTP client (#53, #54) + +**Why.** A did:webvh DID chooses the host its log is fetched from, so a resolver that fetches "whatever the name resolves to" is an SSRF primitive: `did:webvh:{SCID}:localhost%3A` was fetched over plain `http://`, and any other name from any address it resolved to — including cloud-metadata and RFC 1918 space. 0.5.3 had already blocked IP *literals* and redirects; 0.7.0 closes the *name* half. + +- **Breaking**: `DIDWebVHState::resolve()` contacts public hosts only by default (`HostPolicy::PublicOnly`, new `DIDWebVHError::BlockedHost`); the default native client ignores `HTTP(S)_PROXY` (a proxy resolves the name itself, outside the resolver's checks); `ResolveOptions` gains public fields `host_policy` and `http_client` (struct literals need `..Default::default()`). The `ssi`-feature resolver is public-only too. +- **Added**: `host_policy::HostPolicy { PublicOnly, AllowPrivate }`; `guarded_dns_resolver()` / `guarded_dns_resolver_with(inner)` for installing the DNS guard on a caller-built client; `WebVHURL::get_fetch_url(file, policy)` — the policy-checked URL, host canonicalised (percent-decoding, IDNA, case, trailing dot) before checking; `examples/resolve.rs --allow-private-hosts`. Every fetch (`did.jsonl`, `did-witness.json`, eager and deferred) goes through `get_fetch_url()`; under `AllowPrivate` only `localhost` / `*.localhost` use `http://`. +- **Unchanged, now documented as such**: `get_http_url()` / `get_http_whois_url()` / `get_http_files_url()` render URLs for display and the implicit `#files` / `#whois` services and apply no policy. +- **Migration**: local dev/tests → `ResolveOptions::default().with_host_policy(HostPolicy::AllowPrivate)`; trusted private deployments → `AllowPrivate`; proxied environments → pass an `http_client` built with the proxy (name checks still apply). #54 bumped a yanked `wnaf` 0.14.0 → 0.14.1. +- **Downstream effect**: the TDK's `cache-sdk` 0.8.37 exposes one `DIDCacheConfigBuilder::with_host_policy` covering did:web *and* did:webvh; `cache-server` 0.9.13 notes that a deployment reaching did:webvh hosts only through a proxy can no longer resolve them. + +### v0.6.1 — 2026-08-29 — the log entry that *activates* pre-rotation may set `updateKeys` (#52) + +`Parameters::validate()` gated the "every `updateKeys` key must hash into the previous entry's `nextKeyHashes`" rule on the *current* entry's `pre_rotation_active` — which the entry's own new `nextKeyHashes` had just flipped on — rather than the previous entry's. Since a not-yet-pre-rotating predecessor commits no hashes, the activating entry was unsatisfiable, blocking the ordinary operator flow of turning pre-rotation on as part of a document edit that also rotates `updateKeys`. didwebvh 1.0 defines the trigger as the *previous* entry's commitment (verification step 7; update step 7; "in any DID log entry"), and `verify_log_entry()` / `check_signing_key()` already keyed on the previous entry — so a chain written this way would *resolve* but this crate could not *produce* one. Steady-state and deactivation rules unchanged. #51 refreshed the lockfile onto the published TDK line (data-integrity 0.7.10, crypto 0.2.8, did-common 0.4.2, secrets-resolver 0.5.10) and added an audit ignore for RUSTSEC-2026-0235 (`rkyv` via `rust_decimal`, unreachable). ### v0.6.0 — 2026-07-19 — `affinidi-did-common` 0.4 + pre-release audit fixes (#50) diff --git a/content/entities/dtg-credential-spec.md b/content/entities/dtg-credential-spec.md index 6c25872..7a8901a 100644 --- a/content/entities/dtg-credential-spec.md +++ b/content/entities/dtg-credential-spec.md @@ -2,49 +2,105 @@ title: "DTG Core Credentials Specification (ToIP DTGWG)" type: entity tags: [spec, dtg, trust-over-ip, primary, working-draft] -date-updated: 2026-08-19 +date-updated: 2026-09-18 repo: https://github.com/trustoverip/dtgwg-cred-spec --- # DTG Core Credentials Specification (ToIP DTGWG) -*Repo: [github.com/trustoverip/dtgwg-cred-spec](https://github.com/trustoverip/dtgwg-cred-spec) — normative spec, **Version 1.0, Working Draft 01**. Supporting materials and discussions: [github.com/trustoverip/dtgwg-cred-tf](https://github.com/trustoverip/dtgwg-cred-tf).* +*Repo: [github.com/trustoverip/dtgwg-cred-spec](https://github.com/trustoverip/dtgwg-cred-spec) — normative spec, **Version 1.0, Working Draft 02** (tag `WD02`, 2026-09-07). `main` has since moved on to **Working Draft 0.4.0** under a new semantic-versioning scheme for Document Status (2026-09-15). Supporting materials and discussions: [github.com/trustoverip/dtgwg-cred-tf](https://github.com/trustoverip/dtgwg-cred-tf).* The authoritative specification for the [[decentralized-trust-graph|Decentralized Trust Graph]] credential family, maintained by the **Trust over IP Foundation's DTG Working Group Credentials Task Force**. This is the definitive source for DTG credential semantics — what each credential type means, how it's constructed, what proofs it supports, and how the wider trust graph composes from them. Implementations track this spec; where the wiki and the spec disagree, the spec wins. -In July 2026 the spec **moved home and became a formal ToIP deliverable**: the informal `dtg.md` v0.3 that lived in the task-force repo was migrated into the ToIP **Spec-Up-T** template in a new repo, `dtgwg-cred-spec`, as *DTG Core Credentials V1.0 — Working Draft 01*. It now follows the Linux Foundation JDF process — a sequence of numbered Working Drafts until the task force submits it for a vote as a Working Group Approved Deliverable. The old repo (`dtgwg-cred-tf`) deleted its copy of the spec in August (#38) and keeps only supporting materials (`use_cases.md`, `witnessed_vrc_flow.md`, links to the First Person Project whitepaper, VTC Bootstrapping Process, DTG Glossary) plus the **discussions** board; spec-specific issues go to the new repo. RFC 2119 key words are binding only in sections that open with "*This section is normative.*" — the Introduction, Terminology, Taxonomy and all Considerations sections are informative. +In July 2026 the spec **moved home and became a formal ToIP deliverable**: the informal `dtg.md` v0.3 that lived in the task-force repo was migrated into the ToIP **Spec-Up-T** template in a new repo, `dtgwg-cred-spec`, as *DTG Core Credentials V1.0 — Working Draft 01*. It follows the Linux Foundation JDF process — a sequence of Working Drafts until the task force submits it for a vote as a Working Group Approved Deliverable. The old repo (`dtgwg-cred-tf`) deleted its copy of the spec in August (#38) and keeps only supporting materials (`use_cases.md`, `witnessed_vrc_flow.md`, links to the First Person Project whitepaper, VTC Bootstrapping Process, DTG Glossary) plus the **discussions** board; spec-specific issues go to the new repo. RFC 2119 key words are binding only in sections that open with "*This section is normative.*" — the Introduction, Terminology, Taxonomy and all Considerations sections are informative. -The wiki entity for the implementation that tracks this spec is [[dtg-credentials]] (0.2.0 tracks WD01); for the conceptual taxonomy it defines, see [[dtg-credentials-overview]]. +**September 2026 was the spec's biggest month since the migration.** Working Draft 02 (28 commits in five weeks) grew the catalogue from six credential types to eight — adding the **Verifiable Delegation Credential (VDC)** and **Verifiable Authority Credential (VAC)**, the first credentials that *confer* rather than *attest* — retired the four DID types (R/M/C/P-DID) in favour of a holder-declared **correlation scope** (`pairwise` | `directed` | `public`), fully specified the member-issued VMC so a membership edge is actually constructible, and replaced the `sha256:` digest with the W3C **`digestMultibase`** encoding. Then, in the ten days after the tag, `main` went further still: the VAC gained key-control binding, revocation and an attenuation limit; the VIC was promoted out of its single-member category; and the **Verifiable Statement Credential (VSC)** absorbed the VEC and VWC as *predicate profiles*, bringing the type count back down to seven. Numbered drafts were then retired in favour of a semver `Document Status` (currently `0.4.0`). The unversioned `main` is therefore materially ahead of the `WD02` tag; the [[dtg-credentials]] crate tracks WD02 (0.7.0+), not `main`. + +The wiki entity for the implementation that tracks this spec is [[dtg-credentials]] (0.7.0–0.9.1 track WD02; 0.2.0–0.6.0 tracked WD01); for the conceptual taxonomy it defines, see [[dtg-credentials-overview]]. ## Contents -Spec-Up-T layout (`specs.json` → `spec/`), rendered to `docs/`: +Spec-Up-T layout (`specs.json` → `spec/`), rendered to `docs/`. Described as of `main` on 2026-09-15 (Working Draft 0.4.0); where the `WD02` tag differs, it is noted. -- **`header.md`** — title, Version 1.0 / Working Draft 01, editors (Alberto Leon, Brendan A. Miller — Harvard Applied Technology Lab; Geoff Turk, Martina Kolpondinos, Drummond Reed — First Person Project), IPR (CC BY 4.0, W3C patent mode, Apache 2.0) -- **`intro.md`** — abstract ("six W3C Verifiable Credential types that create and annotate the nodes and edges of a DTG"), the three functional categories, a note that this version uses DIDs exclusively as its verifiable identifiers (X.509 / KERI AIDs possible later) -- **`terms-and-definitions-intro.md`** + **`terms-definitions/*.md`** — ~50 glossary terms, one file each, imported from the DTG Glossary: the credential types, DID types, DTG node/edge/VID, and the **VTA topology vocabulary** (personal / community / local / cloud VTA, VTA networks, PNM, PNV, VTSP — see [[vta-topology]]), community roles (initiator, community trust anchor, PEP, IDVP/IDVC), relationship card, relationship invitation (OOBI) -- **`body.md`** — the normative core, in narrative order: Taxonomy → W3C VC version support (v2.0 primary, v1.1 legacy) → Base Structure (incl. the new `taskContext` property) → Edge Credentials (VRC, VMC) → Invitation Credential (VIC) → Annotation Credentials (VPC, VWC, VEC) → **Trust Task Context Binding** → Supporting Concepts (PHC, Trust Registries, IDVC, ZK & Selective Disclosure) → Security / Privacy / Governance / I18N / Accessibility Considerations → Conformance → References +- **`header.md`** — title, Version 1.0 / Document Status *Working Draft 0.4.0* (WD02 tag: "Working Draft 02"), editors (Alberto Leon, Brendan A. Miller — Harvard Applied Technology Lab; Geoff Turk, Martina Kolpondinos, Drummond Reed — First Person Project), abstract (rewritten in #16 and again with each catalogue change), IPR (CC BY 4.0, W3C patent mode, Apache 2.0) +- **`intro.md`** — the credential catalogue and its categories (on `main`: *seven* types — three Edge, two Annotation, two standing outside any category; WD02: *eight* types — three Edge, one Invitation, three Annotation, VAC outside); Related Specifications, now naming three planned companions: **DTG Core Trust Task Protocols**, **DTG Verifiable Data Structures** (r-card, agent card) and, new on `main`, the **DTG Predicate Vocabulary** (a repo-driven registry of VSC predicates, not a spec); and a new **Specification Versioning** section (#54) defining the `_Version:_` / `_Document Status:_` split, Compatibility Rules and the ratification lifecycle +- **`terms-and-definitions-intro.md`** + **`terms-definitions/*.md`** — ~52 glossary terms, one file each. WD02 deleted `community_did`, `membership_did`, `relationship_did`, `persona_did` and added `correlation_scope`, `verifiable_authority_credential`, `verifiable_delegation_credential`; `main` adds `verifiable_statement_credential` and rewrites the VEC/VWC/annotation entries as profiles. The **VTA topology vocabulary** (personal / community / local / cloud VTA, VTA networks, PNM, PNV, VTSP — see [[vta-topology]]) and community roles (initiator, CTA, PEP, IDVP/IDVC) are unchanged +- **`body.md`** — the normative core (1,400 → 1,800 lines across WD02 and `main`), in narrative order: Taxonomy → W3C VC version support (v2.0 primary, v1.1 legacy) → **Correlation Scope** (new, normative) → Base Structure (incl. `taskContext`; informative **DID Method Considerations**; normative **Digest Encoding**) → Edge Credentials (VRC, VMC with **Membership Edge Completion**, **VDC** with Delegation Edges / Chains / Invocation Binding, **Edge Verifiability**) → **VIC** (top-level section on `main`; under "Invitation Credentials" in WD02) → Annotation Credentials (VPC; on `main` the **VSC** with Predicate Handling, What Verification Establishes, Predicate Profiles, the `dtg:endorses` (VEC) and `dtg:witnessed` (VWC) profiles; in WD02 VWC and VEC as concrete types) → **VAC** (Attenuation; on `main` also Invocation and Withdrawal) → Trust Task Context Binding → Supporting Concepts (PHC, Trust Registries, IDVC, ZK & Selective Disclosure — with a long editor's note on what is waiting on the ZKP task force) → Security (22 items on `main`, 18 in WD02, 8 in WD01) / Privacy (14 / 13 / 8) / Governance (9 / 7 / 4) / I18N / Accessibility Considerations → Conformance → References (now also CID v1.0, VC Data Integrity, ZCAP-LD, UCAN, Bitstring Status List) - **`appendix.md`** — acknowledgements -- Companion specs announced but not yet written: **DTG Verifiable Data Structures** (relationship card, agent card) and **DTG Core Trust Task Protocols** (offer / issue / request / present / revoke and the trust-task completion artifact `taskContext` refers to) +- **`design-notes/delegation-design-time-window.md`** (new, non-normative) — the rationale record for the VDC as merged, and what WD02 did *not* take from the August single-hop proposal +- **`CONTEXT.md`** — an LLM/contributor style guide with per-term "avoid" lists; a useful quick reference for current terminology ## Wiki pages derived from this spec -- [[credential-categories]] — The three functional categories (Edge, Invitation, Annotation) -- [[did-types]] — The formal DID taxonomy (C-DID, M-DID, R-DID, P-DID) +- [[credential-categories]] — The functional categories (now *two* — Edge, Annotation — with the VIC and VAC outside both; WD02 had three) +- [[did-types]] — The formal DID taxonomy (C-DID, M-DID, R-DID, P-DID) — **retired in WD02** in favour of correlation scope; page needs rewriting - [[trust-task-context-binding]] — `taskContext`: binding a credential to the exchange that produced it, without making it proof of the outcome - [[vta-topology]] — The VTA vocabulary: personal / community, local / cloud, VTA networks, PNM, PNV, VTSP - [[trust-registries]] — Governance layer that determines PHC status and policies -- [[verifiable-trust-network]] — VTN concept (VTN → VTC hierarchy) -- [[witnessed-vrc-exchange]] — The five-phase witnessed exchange protocol (supporting material in the task-force repo; its per-direction VWC pattern is now normative) -- [[witness-credential]] — VWC with required `taskContext` and `digest` -- [[persona-credential]] — VPC and the "Banksy Maneuver" -- [[invitation-credential]] — VIC with issuer policy details -- [[zero-knowledge-proofs]] — Pairwise vs community-anchored ZKP constructions; ZKPs by default -- Plus updates to [[personhood-credential]], [[membership-credential]], [[relationship-credential]], [[dtg-credentials-overview]], [[decentralized-trust-graph]], and others +- [[verifiable-trust-network]] — VTN concept; WD02's Edge Verifiability makes the VTN one *example* of a verifier's anchor set rather than a precondition +- [[witnessed-vrc-exchange]] — The five-phase witnessed exchange protocol (supporting material in the task-force repo; its per-direction VWC pattern is normative, now for VMC pairs too) +- [[witness-credential]] — VWC with required `taskContext` and `digestMultibase`; on `main` a VSC predicate profile (`dtg:witnessed`) +- [[endorsement-credential]] — VEC; on `main` a VSC predicate profile (`dtg:endorses`) +- [[persona-credential]] — VPC and the "Banksy Maneuver"; the persona identifier is now "ordinarily declared `directed`" +- [[invitation-credential]] — VIC with issuer policy details; now a top-level section outside the categories +- [[membership-credential]] — VMC; both directions now specified, the acknowledgement carries `digestMultibase` of the grant +- [[relationship-credential]] — VRC; `pairwise` RECOMMENDED, Edge Verifiability defined per-verifier +- [[zero-knowledge-proofs]] — Pairwise vs community-anchored ZKP constructions; ZKPs by default; new predicates for chains, shared-subject and statements +- [[decentralized-identifiers]] / [[did-webvh]] — WD02's DID Method Considerations: durable vs narrow-scope identifiers, `did:webvh` in examples, `portable` caveat +- Plus updates to [[personhood-credential]], [[dtg-credentials-overview]], [[decentralized-trust-graph]], [[verifiable-trust-community]], and others +- **Not yet in the wiki**: the VDC, the VAC, correlation scope, and the VSC each warrant a concept page ## Recent Development -The spec is at **v1.0 Working Draft 01** in `dtgwg-cred-spec`; activity is per-PR. Entries before July 2026 refer to PRs in the old `dtgwg-cred-tf` repo. +The spec is at **v1.0 Working Draft 02** (tag `WD02`) in `dtgwg-cred-spec`; `main` carries Working Draft 0.4.0. Activity is per-PR. Entries before July 2026 refer to PRs in the old `dtgwg-cred-tf` repo. + +### `main` after WD02 — 2026-09-10 → 09-15 — the VAC hardened, the VIC promoted, the VSC arrives, and Document Status goes semver + +Ten days after the WD02 tag, seven substantive changes landed on `main`. None is in a tagged draft yet, and [[dtg-credentials]] has not implemented them; they are recorded here because they will be the spine of the next draft. + +- **#54 — 2026-09-15 — semantic versioning for Document Status.** `_Version:_` (1.0) is now the release the group is converging toward and moves only at ratification; `_Document Status:_` carries the Working Draft's own semver, **`Working Draft 0.4.0`** — continuing from the pre-migration v0.3 rather than restarting at 0.1.0, so that "01"/"02" are not mistaken for 0.1/0.2. New Compatibility Rules (MAJOR for breaking, MINOR for additive, PATCH for editorial; a pre-ratification escape clause lets breaking changes stay MINOR while MAJOR is 0; a holder/verifier conformant to `M.N` MUST accept credentials issued under `M.K`, K ≤ N, and SHOULD tolerate unknown OPTIONAL properties) and a Ratification and Stable Release Line subsection, shared with the sibling Trust Tasks and VTI specs. Also corrects the `taskContext` binding's minimum companion version to Trust Tasks Document Status 0.4.0. +- **Commit `5a660ea` — 2026-09-15 — the Verifiable Statement Credential (VSC)** (refs #45, #28, #48, #52; not a numbered PR). The problem: each new "I attest X about Y" predicate was becoming its own credential type, and a type string and a payload could disagree. The fix: **one `StatementCredential` type** carrying `credentialSubject.id` (subject), `predicate` (an absolute IRI from a governed vocabulary — no CURIEs, no JSON-LD expansion, NFC, byte-exact comparison) and `object` (exactly one of `id` | `digestMultibase` | `value`). The **VEC and VWC become its first two predicate profiles**, `dtg:endorses` and `dtg:witnessed`; the type strings `EndorsementCredential` and `WitnessCredential` are **removed**. Three new normative subsections: *Predicate Handling* (verifiers **fail closed** on any predicate not in a configured vocabulary; equivalences like `owl:sameAs` are never followed; predicates resolve at configuration time, never at verification), *What Verification Establishes* (a VSC attests and never establishes — "a predicate named `mayActFor` is a string"), and *Predicate Profiles* (the nine things a profile must state). The VWC's direction binding becomes **unconditional**: `credentialSubject.id` MUST be the issuer of the credential named by `object.digestMultibase`. Type count: eight → seven. The `dtg:` namespace (`https://firstperson.network/credentials/dtg/v1#`) is a placeholder pending #48; predicates beyond the core profiles will live in a **DTG Predicate Vocabulary** registry (#52). Governance gains "which vocabularies a verifier accepts is a governance decision"; a new Security item 7 (*Predicate acceptance*). +- **VIC promoted to a top-level section — 2026-09-10 (closes #28; decision D-B of #31).** "Invitation Credentials" had exactly one member. The categories are now **two** — Edge (VRC, VMC, VDC) and Annotation (VPC, VSC on `main`) — with the **VIC and VAC standing outside both**: neither forms an edge nor annotates one. No normative change; abstract and `CONTEXT.md` follow (#7ca543c). +- **#41 — 2026-09-10 — a VAC is not a bearer credential.** New *Invocation* subsection: a verifier MUST NOT accept a party as holding a VAC's authority unless it demonstrates control of the presented VAC's `credentialSubject.id` at the time of the request — the same rule the VDC already had, and sharper here because a captured presentation is a captured *chain*. Only the leaf's subject demonstrates anything. Consequently the OPTIONAL **`audience` property is removed**: once the presenter must be the subject it can only repeat the subject or be unsatisfiable. New Security item 21 (*Bearer use of a VAC*), and Conformance Target 3 gains the key-control rule. +- **#40 — 2026-09-10 — a governing party can bound or forbid attenuation.** New OPTIONAL **`authority.maxAttenuation`**: `0` forbids attenuation outright; a child MUST NOT bear a value greater than its parent's minus one; the effective limit is the strictest on the chain. The section now argues *why* attenuation is opt-out while re-delegation is opt-in: forbidding attenuation doesn't stop a holder equipping an agent, it makes them lend their key. Deliberately not called `maxDepth` (which fails closed on a VDC). Also new: a governing party MAY require the subject of an attenuated VAC to independently qualify (the authority-side twin of the VDC's third check) — Governance item 8. +- **#39 — 2026-09-10 — how authority is withdrawn from a VAC.** New *Withdrawal* subsection: expiry is the primary mechanism (`validUntil` REQUIRED), `credentialStatus` is CONDITIONAL on the governing party's freshness window (mirroring the VDC), only a VAC's issuer can revoke it, a verifier MUST check status on every chain link that carries one and MUST NOT treat its absence as a defect, and **revocation cascades** — revoking a VAC withdraws everything attenuated from it, which is what makes unregistered attenuation safe. The converse is stated: a chain with no status cannot show an ancestor was revoked, so exposure is bounded by the shortest `validUntil`. Security items 20 and 22, Privacy item 14 (a root's status list observes the timing of agents the governing party never issued to), Governance item 7. +- **#42 — 2026-09-10 — what the ZK predicates are waiting on.** The Zero-Knowledge section now says plainly that four of its predicates rest on one unbuilt primitive — a proof of **common control** of two credentials or identifiers (#9) — which carries the community-anchored ZKP wherever VMC and VRC identifiers differ (the ordinary case under correlation scope), the shared-subject requirement, and ZK forms of both chains; the unsalted digest binders are #38. It points at the ZKP task force's working draft and construction catalogue (records 007, 010, 020, 008) and states that nothing in the spec is unverifiable meanwhile — "the cost is privacy rather than correctness." + +### `WD02` — 2026-09-07 — Working Draft 02 (#44), the merge sequence #12 → #20 → #16 → #27 → #26 → #30 → #19 → #29 + +The tag sits on the status-line bump (#44) after five PRs landed in three days (#27, #26, #30, #19, #29), on top of three from late August (#12, #20, #16). A caveat on the tags: both `WD01` and `WD02` were created retroactively on 2026-09-07, and **`WD01` was placed at #37 (2026-09-05)** — so the tagged `WD01..WD02` diff contains only the five September PRs, while #12, #20 and #16 (all merged *after* the wiki's last refresh recorded WD01 at PR #14) are technically inside the `WD01` tag. This log treats all eight as the WD01 → WD02 delta, since that is what changed for implementers. Infrastructure PRs in the same window: spec-up-t 1.9.0 (#33), Dependabot (#32), Actions pinned to SHAs (#37), README backlink (#34). + +#### PR #29 — 2026-09-07 — the Verifiable Authority Credential (VAC) + +The catalogue had six credentials and none conferred permission, so implementations were packing authority into a VEC — putting a decision that belongs to the governing party into a claim that reads as reputation. The VAC is "a statement *to* a verifier" rather than *about* a party: `issuer` governs a scope, `credentialSubject.authority` carries `scope` (DID or URI; exact match unless the governing party publishes containment) and `actions` (non-empty; empty confers *nothing*; no action implies another). **Attenuation** is what earns its place: a holder MAY issue a narrower VAC — `issuer` MUST be the parent's subject, `authority.parent` is the parent's **digest**, no new actions, no later `validUntil`, no wider scope — so a person's AI agent can carry four hours of read-only access instead of the person's standing authority. Verifiers MUST walk the whole chain to a VAC from the governing party and reject any widening link; **the holder presents the chain, the verifier never fetches it**; depth capped at **8**. `validUntil` REQUIRED. Two rules recorded where implementers will meet them: *authority is not delegation* (a VAC never authorizes acting on behalf of another) and *authority and membership are separate credentials* (proving both in ZK with the subject withheld MUST include a shared-subject proof, else two parties pool credentials). Placed as a top-level section outside the three categories rather than a single-member fourth category. Also resolves the VIC editor's note: roles and access control do *not* belong in the invitation. Security items 15–18, Privacy item 13 extended. (WD02 shipped with an OPTIONAL `audience`; removed on `main` by #41.) + +#### PR #19 — 2026-09-06 — the Verifiable Delegation Credential (VDC) and the `digestMultibase` encoding + +The glossary already assumed delegation ("the person it identifies (or their delegate)"), AI agents are first-class nodes, but nothing could say an agent may act *for* a person. The VDC is the **seventh type and third edge credential**: `issuer` = delegator, `credentialSubject.id` = delegate, `credentialSubject.delegation` carries `scope` (non-empty, opaque strings compared for exact equality), optional `parent` (digest), optional `maxDepth` (absent or `0` prohibits re-delegation — **single-hop by default**), and, on the acceptance, `accepts` (digest of the grant). **A grant plus a matching acceptance forms the edge, and the acceptance is REQUIRED** — the same consent logic as the VMC pair: a delegator cannot forge the countersignature. The spec draws the line hard: *delegation is not authority*. A VDC "moves the permission question; it does not answer it" — the verifier substitutes the delegator and asks whether *the delegator* may act; reach is the **intersection** of the appointment and the delegator's own permission; nothing the delegator holds is copied. `validUntil` REQUIRED; `credentialStatus` CONDITIONAL (required only where validity exceeds the governing freshness window — short validity and re-issuance is the preferred default, because a status lookup is a correlation surface). *Invocation Binding*: a VDC is not a bearer token — the presenter MUST demonstrate control of `credentialSubject.id`. Grant vs invocation is split by the spec's own credential/artifact test (the invocation is a Trust Task artifact). Security items 9–14 (incl. *personhood laundering via delegation*), Privacy items 10–13, Governance items 5–6. A design note records the August single-hop-only proposal and what WD02 did and didn't take from it. + +The same PR added the normative **Digest Encoding** section: every cross-credential reference — the member-issued VMC's and the VWC's `digestMultibase`, the VDC's `parent`/`accepts`, the VAC's `authority.parent` — is the SHA-256 of the referenced credential's JCS canonical form **excluding its top-level `proof`**, wrapped as a `sha2-256` Multihash and base58btc-encoded with the `z` Multibase prefix, exactly as VC Data Integrity §2.6 defines `digestMultibase`. This **replaces WD01's bespoke `sha256:`** and renames the VWC/VMC property `digest` → `digestMultibase`. Verifiers MUST compare decoded bytes, never strings; a digest naming an unaccepted hash algorithm MUST be rejected rather than treated as a mismatch. Excluding `proof` means a re-signed grant still satisfies an existing acknowledgement. An editor's note records that none of the digests is salted (#38). Note the irony for [[dtg-credentials]]: the crate's original 0.2.0 divergence was that it emitted exactly this multibase form; WD02 moved to the crate's encoding (minus the proof), not the other way round. + +#### PR #30 — 2026-09-05 — the four VID types are replaced by a declared correlation scope + +Resolves #22 and cred-tf discussion #41. R-DID, M-DID, C-DID and P-DID each packed two independent things into one name — *what the identifier is attached to* and *how widely it may be correlated* — and the two disagree as soon as an M-DID appears in a VRC (which the VRC section permitted). The role is already carried by the credential the identifier appears in; only the width needs declaring. So: a VID MAY carry a holder-declared **correlation scope**, one of three ordered values — **`pairwise`** (one counterparty), **`directed`** (a set the holder chooses — the ordinary home of a persona), **`public`** (unbounded; a VTC's identifier can only truthfully be `public`). "Roles are conferred by credentials; scope is declared by the holder." Three values not four: a community-bounded value would take its bound from a credential rather than the holder — the exact conflation being removed. New normative subsection *Scope the holder cannot declare alone*: a VTC issuing VMCs **MUST publish** whether member identifiers are disclosed beyond the VTA, and a verifier MUST NOT infer from a member's `pairwise` declaration that the community treats it so. Under strict pairwise, the identifier toward the VTC and toward a counterparty differ by construction, so the community-anchored ZKP must prove common control — members expecting to prove such relationships will in practice declare `directed`. **The property that carries the declaration is not yet named**; until it is, no credential is non-conforming for lacking one. Four glossary entries deleted outright; `CONTEXT.md` now lists R/M/C/P-DID under *Avoid*. Reflected (as stale) in [[did-types]]. + +#### PR #26 — 2026-09-05 — Edge Verifiability defined normatively + +Fixes #21, reported independently by two implementations: the conditions for a VRC being "verifiable as a DTG edge" lived only in the glossary, contradicted the body, and one (both peers signing one VRC) was unsatisfiable by any credential. New normative section: an edge credential is verifiable as a DTG edge **by a given verifier** when its proof verifies and the verifier can establish, per Membership Edge Completion, that the issuer's membership in a VTC **in the anchor set that verifier accepts** is complete. A VTN is the common anchor set but need not exist. Condition 2 MAY be met **by disclosure** or **by proof** (community-anchored ZKP), and the two are equivalent in principle — a `pairwise` did:peer VRC with a valid proof is an edge on the same terms as one issued from the VMC's own identifier. Each half is evaluated independently. Glossary VRC/VMC entries reduced to pointers. Bears on [[verifiable-trust-network]] and [[relationship-credential]]. + +#### PR #27 — 2026-09-05 — effective disclosure of an edge + +Addresses #23. A declared scope binds only its holder; the effective disclosure of an edge is the **wider** of its two halves — a correctly pairwise half is still correlated to a named party if the counterparty published the opposing half under a `directed`/`public` identifier. Generalised to any joint presentation. Privacy item 9. + +#### PR #12 — 2026-08-28 — both VMC directions specified, so membership requires member consent + +Issue #8 flagged a structural contradiction: five places said two VMCs form an edge, but the schema made the reverse credential unconstructible. The two directions are now distinguished by issuer/subject rules and by the presence of a digest, not by type strings: the **community-issued VMC (grant)** goes from the VTC/VTN to the member; the **member-issued VMC (acknowledgement)** reverses the roles and carries a digest of the grant. New *Membership Edge Completion* subsection: the grant MUST be issued first; an acknowledgement whose digest matches no valid grant MUST NOT complete the edge; where anyone *other than the member* asserts membership the verifier MUST require the acknowledgement, and a community asserting membership MUST be able to produce it — **the acknowledgement is the member's consent artifact**, which makes unconsented membership claims unprovable. Deliberately direction-sensitive: a member presenting their own grant with proof of key control MAY be accepted alone (the presentation *is* the consent), which is what keeps the community-anchored ZKP and PHC constructions working. Per-direction witnessing generalised from VRCs to both edge subtypes. Lifecycle (withdrawal, revocation, re-issuance) deferred to the Trust Task Protocols spec, with two interim controls via short `validUntil`. Security item 7; Privacy item 8 (the acknowledgement as a disclosure artifact the community holds). Reflected in [[membership-credential]]. + +#### PR #20 — 2026-08-27 — `did:webvh` in examples; DID Method Considerations + +Examples switched from `did:web` (no verifiable history, no recovery on compromise) to `did:webvh` for durable issuers; subjects stay on `did:key`/`did:peer`. New informative *DID Method Considerations*: the spec mandates no method but says what it relies on one for. **Durable identifiers** (a VTC's, a member's, a witnessing VTA's) need verifiable key history, rotation without changing the identifier, pre-rotation, independence from a single operator (note: `did:webvh` is relocatable only if `portable` was set in the *first* log entry; `did:scid` avoids the choice), and discoverable endpoints. **Narrow-scope identifiers** need cheap creation, no shared resolution origin, and no infrastructure that observes their use — so a DID log witness set or a shared web origin is itself a correlation channel (Privacy item 4). Durability and scope are independent axes; expect to mix methods. Bears on [[did-webvh]] and [[decentralized-identifiers]]. + +#### PR #16 — 2026-08-28 — abstract rewritten for readability ### dtgwg-cred-tf #38 — 2026-08-18 — old repo retires its copy of the spec @@ -60,13 +116,13 @@ Taxonomy diagram flipped to left-to-right in the order VRC, VMC, VIC, VPC, VWC, ### PR #7 — 2026-07-30 — implementation feedback from Keyring Wallet: VWC digest canonicalisation + per-direction witnessing -Two fixes driven by a real implementation (Keyring Wallet's witness server): (a) v0.3 said the digest was "a multibase string (multihash + multibase)" while its example showed `sha256:` — contradictory and unimplementable. Now: **the digest MUST be the SHA-256 of the credential's JSON canonicalised with JCS (RFC 8785), encoded as the string `sha256:` followed by the lowercase hex digest.** (b) **Per-direction witnessing**: a witnessed exchange of a complete edge is bidirectional — two VRCs, one each way, in a single witnessing event — so the witness SHOULD issue **one VWC per direction**, and in each VWC `credentialSubject.id` MUST be the DID of the *issuer* of the VRC being attested. Note the [[dtg-credentials]] crate (0.2.0) encodes the digest as multibase/multihash (`z…`, the W3C `digestMultibase` convention) instead — a flagged, unresolved divergence. +Two fixes driven by a real implementation (Keyring Wallet's witness server): (a) v0.3 said the digest was "a multibase string (multihash + multibase)" while its example showed `sha256:` — contradictory and unimplementable. WD01 settled on: **the digest MUST be the SHA-256 of the credential's JSON canonicalised with JCS (RFC 8785), encoded as the string `sha256:` followed by the lowercase hex digest.** (b) **Per-direction witnessing**: a witnessed exchange of a complete edge is bidirectional — two VRCs, one each way, in a single witnessing event — so the witness SHOULD issue **one VWC per direction**, and in each VWC `credentialSubject.id` MUST be the DID of the *issuer* of the VRC being attested. The [[dtg-credentials]] crate (0.2.0) at the time encoded the digest as a multibase/multihash (`z…`) instead — a flagged divergence that the crate fixed in 0.4.0 by adopting the hex form, only for WD02 (#19) to move the spec itself to the multibase `digestMultibase` encoding (minus `proof`), which the crate then adopted in 0.7.0. The encoding question is now closed on both sides. ### PR #3 — 2026-07-13 — migrate v0.3 into the Spec-Up-T template (Working Draft) Beyond the re-templating, several substantive changes: - **New normative section "Trust Task Context Binding"** and a new base-structure property **`taskContext`** (the `threadId` of the trust-task exchange in which the credential was issued): OPTIONAL on all types, **REQUIRED on VWC**. A credential without `taskContext` MUST be interpretable standing alone; a verifier MUST NOT treat a `taskContext`-bearing credential as proof the associated task *completed* unless the matching outcome evidence is present and verified. Informative test: "true outside the exchange? → credential; only meaningful inside? → artifact." See [[trust-task-context-binding]]. -- **W-DID dropped** from the identifier taxonomy — the VWC issuer is "an M-DID, or the DID of a VTA acting according to VTC policy"; four official DTG identifier types remain (R-DID, M-DID, C-DID, P-DID). Reflected in [[did-types]]. +- **W-DID dropped** from the identifier taxonomy — the VWC issuer is "an M-DID, or the DID of a VTA acting according to VTC policy"; four official DTG identifier types remain (R-DID, M-DID, C-DID, P-DID). Reflected in [[did-types]]. (All four were themselves retired in WD02.) - **RCard section removed**; the relationship card is a VDS, not a `DTGCredential`, and moves to the planned *DTG Verifiable Data Structures* spec (alongside a planned agent card). The category count accordingly drops **four → three** (VDS gone). Reflected in [[credential-categories]], [[dtg-credentials-overview]]. - VWC purpose broadened (the witness may be a person *or a VTA applying VTC witnessing policy* — e.g. same-event presence, biometric liveness); VIC explicitly has two functional variants — VTC invitation and VTN invitation — distinguished by issuer/subject rules, not type strings; VPC purpose adds "control intentional correlation across relationships". - New **Security Considerations** (7 items — incl. VIC replay: short validity, single-use at the accepting VTA/PEP), **Privacy** (2 → 6 items), **Governance** (4 — "whether a VMC qualifies as a PHC is a governance determination, not a schema property"), **Conformance** targets (issuers / holders / verifiers; no test suites yet), I18N/Accessibility stubs, normative/informative references. diff --git a/content/entities/dtg-credentials.md b/content/entities/dtg-credentials.md index 7bd9a11..1e3328d 100644 --- a/content/entities/dtg-credentials.md +++ b/content/entities/dtg-credentials.md @@ -1,8 +1,8 @@ --- title: "dtg-credentials — Trust Graph Credential Library" type: entity -tags: [dtg, credentials, library, trust-over-ip, primary, cypress] -date-updated: 2026-08-19 +tags: [dtg, credentials, library, trust-over-ip, primary, cypress, dogwood] +date-updated: 2026-09-18 repo: https://github.com/OpenVTC/dtg-credentials --- @@ -10,56 +10,127 @@ repo: https://github.com/OpenVTC/dtg-credentials *Repo: [github.com/OpenVTC/dtg-credentials](https://github.com/OpenVTC/dtg-credentials)* -A Rust library implementing the [[decentralized-trust-graph|Decentralized Trust Graph]] credential types. It provides the data structures and signing/verification logic for the [[verifiable-credentials|Verifiable Credentials]] that form the edges of the trust graph. +A Rust library implementing the [[decentralized-trust-graph|Decentralized Trust Graph]] credential types. It provides the data structures, signing/verification logic, digest binding and — since September 2026 — the **chain verifiers** for the [[verifiable-credentials|Verifiable Credentials]] that form the edges of the trust graph and confer authority within it. ## What It Implements -The [[dtg-credential-spec|DTG Core Credentials specification]] from the Trust over IP Foundation's DTG Working Group Credentials Task Force — **v1.0 Working Draft 01** since crate version 0.2.0 (August 2026; 0.1.x tracked the informal v0.3). See [[dtg-credentials-overview|DTG Credential Types]] for the full taxonomy. +The [[dtg-credential-spec|DTG Core Credentials specification]] from the Trust over IP Foundation's DTG Working Group Credentials Task Force — **v1.0 Working Draft 02** since crate version **0.7.0** (2026-09-08). Latest published release is **0.9.1** (2026-09-09); **0.10.0** is on `main` (CHANGELOG dated 2026-09-11, not yet tagged). Earlier: 0.2.0–0.6.0 tracked WD01 (0.6.0 additionally tracked the then-unmerged VAC/VDC PRs); 0.1.x tracked the informal v0.3. See [[dtg-credentials-overview|DTG Credential Types]] for the full taxonomy. + +What the crate does **not** yet implement, all of it post-WD02 work on the spec's `main` (see [[dtg-credential-spec]]): the **VSC** and the removal of the `EndorsementCredential`/`WitnessCredential` type strings; `authority.maxAttenuation` (spec #40); VAC revocation via `credentialStatus` with cascade (spec #39 — `credentialStatus` is modelled and settable but never *resolved*, for any type); and the correlation-scope declaration (spec #30), which the spec itself has not yet given a property name. The README tracks these as explicit notes rather than leaving them silent. ## Architecture -The library is compact — two source files and an example: +The library grew from two source files to four, plus a second example and a test suite that is "mostly attacks": -- **`lib.rs`** — Core types: `DTGCredential` wrapper, `DTGCommon` (W3C VC structure, including the WD01 `taskContext` property), `DTGCredentialType` enum, `CredentialSubject` variants (Basic, Endorsement, Witness; RCard *deprecated*), signing/verification methods, VWC digest helpers (`digest_multibase()`, `verify_digest()`) -- **`create.rs`** — Builder methods for each credential type (`new_vmc`, `new_vrc`, `new_vic`, `new_vpc`, `new_vec`, `new_vwc`; `new_rcard` deprecated) +- **`lib.rs`** — Core types: `DTGCredential` wrapper, `DTGCommon` (W3C VC structure incl. `id`, `taskContext`, `credentialStatus`, and an `extra` map that preserves unmodelled top-level members through a round trip), `DTGCredentialType` enum (`#[non_exhaustive]`, `PartialEq`), `CredentialSubject` variants (Basic, **Membership**, Endorsement, Witness, **Authority**, **Delegation**; RCard *deprecated*), signing/verification, `validate()`, the digest family (`digest_multibase()` / `digest_multibase_json()`, `verify_digest()`, `digests_match()`, `decode_digest_multibase()`; WD01 `digest()` / `digest_json()` deprecated), and the `DTGCredentialError` enum (`#[non_exhaustive]` since 0.10.0) +- **`create.rs`** — Builder methods for each type (`new_vmc`, `new_vrc`, `new_vic`, `new_vpc`, `new_vec`, `new_vwc`, `new_vac`, `new_vdc`; `new_rcard` deprecated), the edge-completing constructors that read both parties off a received grant (`new_member_vmc_for`, `new_delegate_vdc_for`; the non-`_for` forms deprecated), `attenuate` / `attenuate_from_json`, `redelegate` / `redelegate_from_json`, the binding checks `acknowledges()` / `accepts()`, and `with_id()` / `with_credential_status()` +- **`authority.rs`** — `verify_chain` for VACs: the chain must reach a root issued by the governing party; no link may add an action, widen scope, or outlive its parent; each link's issuer must be its parent's subject; the leaf must grant to the *presenter*; depth ≤ 8; every link carries `validUntil`. Resolution is bearer-side — `parent` is a digest and is never dereferenced +- **`delegation.rs`** — `verify_chain` for VDCs: scope subset, monotone expiry, each link issued by its parent's delegate, depth budget narrowing via `maxDepth`, root issued by the principal, leaf appoints the presenter. Returns what the chain *appoints for* (`VerifiedDelegation { principal, .. }`) and deliberately not whether the act is permitted +- **`examples/`** — `sign_and_verify` (one credential) and `data_room` (a whole data room end to end: VAC to owner, VIC, VMC pair, sealed record, an agent attenuated to strictly less, a service appointed by VDC, epoch rotation on removal) +- **`tests/`** — `authority_chain`, `delegation_chain`, `membership_edge`, `json_bounds` ## Usage ```rust -// Create a Persona Credential -let credential = DTGCredential::new_vpc(issuer_did, subject_did, valid_from, valid_until); - -// Sign it -credential.sign(&secret, None).await?; - -// Verify it -credential.verify_proof_with_public_key(&public_key)?; +// Community side: grant membership (set `id` before signing — the proof covers it) +let grant = DTGCredential::new_vmc(community_did, member_did, valid_from, valid_until, false) + .with_id(format!("urn:uuid:{}", Uuid::new_v4())); +grant.sign(&community_key, None).await?; + +// Member side: verify the grant *in its wire form*, then acknowledge it as yourself +verify_grant_with_public_key(&grant_json, &community_public_key, Utc::now())?; +let mut ack = DTGCredential::new_member_vmc_for(&grant_json, &member_did, Utc::now(), valid_until)?; +ack.sign(&member_key, None).await?; + +// Either side: is the edge complete (types, mirrored parties, digest)? +assert!(ack.acknowledges(&grant)?); ``` -The library supports both W3C VC 1.1 and 2.0, handling field name differences (`issuanceDate`/`validFrom`, `expirationDate`/`validUntil`) transparently via serde deserialization. +The library supports both W3C VC 1.1 and 2.0, handling field name differences (`issuanceDate`/`validFrom`, `expirationDate`/`validUntil`) transparently via serde deserialization. A recurring theme in the API is **"digest what you received, not what you parsed"**: anything that arrived from a counterparty goes through the `_json` / `_from_json` forms, because a timestamp is normalised on the way out and the re-emitted bytes may hash differently. ## Dependencies -- `affinidi-data-integrity` — W3C Data Integrity proof creation/verification (EdDSA JCS 2022) -- `affinidi-secrets-resolver` — key management -- `serde` — JSON serialization with camelCase and untagged enum dispatch -- `serde_json_canonicalizer` (JCS / RFC 8785), `sha2`, `multibase` — VWC digest computation (since 0.2.0) +- `affinidi-data-integrity` 0.7 — W3C Data Integrity proof creation/verification (EdDSA JCS 2022) +- `affinidi-secrets-resolver` 0.5 — key management (optional; feature `affinidi-signing`, on by default) +- `serde` / `serde_json` — JSON serialization with camelCase and untagged enum dispatch +- `serde_json_canonicalizer` (JCS / RFC 8785), `sha2` 0.11, `multibase` — digest computation +- `chrono`, `thiserror`, `tracing` +- Dev: `affinidi-tdk` 0.12 (examples' DIDs and signing), `chacha20poly1305`, `rand` (the `data_room` example seals records for real) +- CI (since 0.6.0): GitHub Actions `ci` and `publish` workflows, actions pinned to SHAs, Dependabot; publish checks crates.io for an already-published version before authenticating ## Provenance -Originally developed under `LF-Decentralized-Trust-labs`, migrated to the `OpenVTC` GitHub organization in spring 2026. Published on crates.io since 0.1.1 (2026-03-29; 0.1.0 was never published). MSRV 1.95. +Originally developed under `LF-Decentralized-Trust-labs`, migrated to the `OpenVTC` GitHub organization in spring 2026. Author: Glenn Gore (Affinidi). Published on crates.io since 0.1.1 (2026-03-29; 0.1.0 was never published). MSRV 1.95, edition 2024. ## Recent Development -After a quiet spring, the crate made its first semantic move in August 2026: **0.2.0 tracks the spec's v1.0 Working Draft 01** (see [[dtg-credential-spec]]) — and in doing so fixed a live interoperability bug. It is part of the coordinated **`Cypress`** release ([[coordinated-releases]]; the `Cypress` and `VTI-Cypress-RC-1` tags both point at the 0.2.0 HEAD; `Banyan` = `RC-0` = 0.1.3). The [[verifiable-trust-infrastructure|VTI]] (#916) and [[openvtc]] (#205) moved onto 0.2 the same day — VMC/VEC/VIC bytes are unchanged, so the bump was painless for them. +**Why the version went from 0.2.0 to 0.9.1 in thirty days.** Not a re-numbering to match the spec. The crate is on a 0.x line where every minor bump is, by Cargo's rules, a breaking change — and there were eight of them in a row, each with a reason: three were **live interoperability bugs** found by the OpenVTC and VTI consumers (no `id`, an unconstructible member-issued VMC, a digest over re-serialised JSON), one added the **VAC and VDC** while they were still spec drafts, one brought the crate up to **Working Draft 02** (which changed the digest encoding on the wire), two closed **bearer-credential findings** (a VAC or VDC chain accepted from anyone holding a copy), and 0.10.0 added issue-time validation. The one deliberate oddity is 0.9.1, which is API-breaking despite the patch number because 0.9.0 had been published hours earlier with no consumers. The README now carries an **Upgrading** table stating, for every breaking release, that *verifiers move first*. + +**Coordinated releases** ([[coordinated-releases]]): the crate is part of **`Dogwood`** — `VTI-Dogwood-RC-1` (2026-08-29) is **0.3.0**; `VTI-Dogwood` (2026-08-30, tag-only "silent" release) and `VTI-Dogwood-R1` (tag created 2026-09-01) both point at the **0.5.0** HEAD. `Cypress` = 0.2.0; `Banyan` = 0.1.3. The Eucalyptus RC-0 (2026-09-17) has not been tagged in this repo. + +**On the two divergences flagged at 0.2.0.** The *digest encoding* divergence is resolved — twice over. 0.4.0 fell into line with WD01 (`sha256:`, over JCS *excluding* `proof`); then WD02 (spec #19) itself moved to the multibase `digestMultibase` form the crate had originally used, and 0.7.0 adopted it — so the encoding is now identical on both sides and compared as decoded bytes. The *undeclared `Option` gap* is **still open**: `CredentialSubjectWitness::digest_multibase` is `Option` and `new_vwc(...)` still takes `digest: Option`, although the spec has had it REQUIRED on a VWC since WD01 PR #14. The field's rustdoc now says "REQUIRED by the specification", but nothing refuses a VWC built or parsed without one (compare `MissingTaskContext`, which does). + +### v0.10.0 — 2026-09-11 (on `main`, untagged) — issue-time validation; answering a grant only on your own behalf (#28, #29) + +No signatures change and nothing changes on the wire for a well-formed credential, but several calls now refuse input they used to accept: + +- **`new_member_vmc_for(grant, member, ..)` / `new_delegate_vdc_for(grant, delegate, ..)`** replace the non-`_for` forms (deprecated). The old constructors read the answering party off the grant and had nothing to compare it against; the new ones take the identity whose key will sign and refuse a mismatch with `NotTheGrantSubject` — the same shape of fix `verify_chain` got in 0.8.0/0.9.1: the obligation becomes a parameter. Both also refuse an answer that outlives its grant (`OutlivesGrant`). +- **`verify_grant_with_public_key`** (feature `affinidi-signing`) verifies a grant in its wire form before it is answered: proof present and valid, proof's `verificationMethod` belongs to the grant's `issuer`, window well-formed and containing the instant. +- **A validity window must open before it closes** (`InvalidValidityWindow`), enforced by every `Result`-returning constructor, by the new `validate()`, and by `sign()`. +- **Open JSON is bounded in depth** (`MAX_JSON_DEPTH` = 64, `JsonTooDeep`): a `Value` nested a few thousand levels deep in `endorsement`, `credentialStatus` or `extra` overflowed the stack and aborted the process. The check runs ahead of everything that recurses, including `verify_proof_with_public_key`. +- `DTGCredentialError` is now `#[non_exhaustive]` (seven new variants) — the reason this is 0.10.0 rather than 0.9.2. + +### v0.9.1 — 2026-09-09 — a VDC is not a bearer credential either; upgrade ordering (#26) + +Closes findings #22/#23. `delegation::verify_chain` takes a `presenter` and requires the leaf to appoint it (`NotTheDelegate`) — WD02's *Invocation Binding* rule stated normatively. 0.8.0 had done this for the VAC and left the VDC alone; "delegation is if anything the sharper case: a captured VAC replays whatever it confers; a captured VDC replays *as somebody*." New README **Upgrading** section (verifiers before issuers, for 0.7.0, 0.8.0 and 0.9.1 alike — a 0.6 verifier handed a 0.7 credential reports a *broken chain*, because it compares a digest against an `id`). `BrokenLink` error docs corrected to say `digestMultibase`. **Breaking despite the patch version**, chosen deliberately. + +### v0.9.0 — 2026-09-09 — `credentialStatus` settable; `PartialEq` on the type (#24, #25) + +Two additive items from the conformance audit (#10). `with_credential_status()` / `set_credential_status()` attach a status entry to a credential being built — a setter rather than a constructor parameter because on a VDC it is CONDITIONAL on a governance-defined freshness window the library cannot know. Neither chain verifier resolves it; revocation remains a live lookup the caller performs. + +### v0.8.0 — 2026-09-09 — a VAC is not a bearer credential; `audience` removed (#21) + +`authority::verify_chain` already took a `presenter`, but only compared it against the leaf's optional `audience` — so a leaf without one was accepted from **anybody**. It now requires the leaf to grant to `presenter` (`NotThePresenter`), implementing spec PR #41. Both known consumers (`vti-rooms-dtg`'s chain verifier and nomination check) had already hit the gap and re-compared the subject by hand; "two copies of a check is one place for it to be forgotten, so it moves here." **`authority.audience` removed** from the struct, from `attenuate` / `attenuate_from_json` (each loses its trailing `Option`) and from the verifier — it was also being read two incompatible ways (this crate: the presenter; the `rooms/keys/present/0.1` trust task: the host the presentation is *for*; see dtgwg-trust-tasks-tf#414). The destination question belongs to the trust task document's `recipient`. + +### v0.7.0 — 2026-09-08 — Working Draft 02: digest encoding, VAC parent digests, and the real VDC (#20) + +Both drafts 0.6.0 tracked merged (VAC #29, VDC #19) and the digest encoding changed underneath them; **breaking on the wire and in the API**. + +- **Digest encoding**: WD02 replaced `sha256:` with a base58btc multibase multihash and renamed the property `digest` → **`digestMultibase`**. `digest_multibase()` is un-deprecated and now *excludes* `proof`; `digest_multibase_json()` is its wire-form twin; `digest()` / `digest_json()` deprecated but kept so a migrating caller can recompute an old digest. The old property name `digest` is still accepted on parse; an old *value* fails with `InvalidDigest` rather than a silent mismatch. **Digests are compared as decoded bytes, never strings**; a digest naming an unsupported algorithm is rejected (`UnsupportedDigestAlgorithm`), not treated as a mismatch. +- **`authority.parent` is a digest, not an `id`**: `attenuate` no longer needs the parent to carry an `id` (`AttenuationParentHasNoId` deprecated, never returned); new `attenuate_from_json` for a VAC that arrived from a counterparty. +- **The VDC becomes real**: 0.6.0 had shipped `new_vdc` as a type string over a bare subject. Now `DelegationGrant` (`scope`, `parent`, `maxDepth`, `accepts`), `new_vdc` takes the appointment (non-empty scope, required `valid_until`, optional `max_depth`), `new_delegate_vdc` builds the REQUIRED acceptance from the grant's wire form, `accepts()` checks the binding, `redelegate` / `redelegate_from_json` (re-delegation opt-in via `maxDepth`), and `delegation::verify_chain`. +- **`validUntil` REQUIRED on a VAC and a VDC** (`DateTime` not `Option`; verifiers reject `NoExpiry`). +- `DTGCommon::credential_status` modelled and `DTGCommon::extra` preserves unmodelled members — both because a parse-then-re-serialise used to drop them silently and change the digest. +- Dependencies: `sha2` 0.10 → 0.11 (the library had been linking two copies and hashing with the older one), `affinidi-tdk` 0.10 → 0.12 (dev). +- Deliberately not implemented: VAC revocation cascade (#39), `maxAttenuation` (#40); `audience` kept until #41 landed (removed in 0.8.0). Correlation scope (#30): nothing to implement yet — only the retired DID-type names dropped from docs. + +### v0.6.0 — 2026-09-03 — the VAC and VDC arrive, tracking spec drafts (#15, #16, #17, #18) + +Adds "the two credentials that confer rather than assert", against spec PRs #29 and #19 while still open. `DTGCredentialType::{Authority, Delegation}`, `AuthorityGrant` (`scope`, `actions`, optional `parent`, `audience`), `new_vac` / `new_vdc`, `attenuate`, and **`authority::verify_chain` — "the part that matters"**: anyone can mint a well-formed VAC naming any scope, and it verifies perfectly as a credential; what makes it worthless is that its chain does not reach the governing party. Seven rules, depth bounded at 8, resolution bearer-side (never dereferences `parent`), an empty `actions` list refused at construction and at the deserialization boundary. New `data_room` example. First CI: `ci` and `publish` workflows (#17), and the examples declare `required-features = ["affinidi-signing"]`. + +### v0.5.0 — 2026-08-30 — digest the grant a member received, not a parse of it (#14) — `VTI-Dogwood`, `VTI-Dogwood-R1` + +0.4.0's `new_member_vmc` took a parsed `DTGCredential` and digested it. `DTGCommon` did not then model `credentialStatus`, which every VMC issued against a status list carries, so parsing a received grant and re-serialising it dropped that member — and the acknowledgement went out carrying a digest over a document the community never issued. Both credentials verify; only the digest comparison fails, with nothing to say why. **BREAKING**: `new_member_vmc` takes the grant as `&serde_json::Value` — the JSON the community sent; new `digest_json()`. + +### v0.4.0 — 2026-08-30 — the member-issued VMC becomes expressible; WD01 digest form adopted (#13) + +The spec (WD01 #12) defines membership as a *pair* — grant plus an acknowledgement carrying a digest of the grant — but `CredentialSubjectBasic` was `deny_unknown_fields` over `id` alone, so an acknowledgement could not be built or parsed as a VMC at all. New `CredentialSubjectMembership` (with OPTIONAL `digest`), `new_member_vmc()`, `acknowledges()`, and the spec's digest: **`sha256:` + lowercase hex over JCS excluding `proof`** — one computation serving both the acknowledgement and the VWC. **This closed the 0.2.0 encoding divergence**: `digest_multibase()` (which had included `proof` and used multibase) was deprecated and `verify_digest()` switched to the conformant form. **BREAKING**: a `MembershipCredential` now deserializes with `CredentialSubject::Membership` (normalised in `TryFrom` because a `{ id, digest }` subject is shape-identical to a VWC's); a VMC whose subject fits none of the shapes is refused. + +### v0.3.0 — 2026-08-29 — a credential gets its own `id` (#12) — `VTI-Dogwood-RC-1` + +`DTGCommon` had no top-level `id`, so a crate-built credential could not carry one — and **every reciprocal MembershipCredential an OpenVTC member issued was being rejected by the VTC**, with the rejection arriving as a problem-report the member's client discarded, so the failure was silent on both sides. `with_id()` / `set_id()` / `id()`; `id` MUST be set before `sign()` (a test pins that). **BREAKING** only for exhaustive struct literals of `DTGCommon`. + +### #11 — 2026-08-28 — `affinidi-tdk` 0.10 (dev) and TDK lock refresh + +### v0.2.0 — 2026-08-10 — track DTG Core Credentials WD01 (#8, `feat!`; release notes #9) — `Cypress` -### v0.2.0 — 2026-08-10 — track DTG Core Credentials WD01 (#8, `feat!`; release notes #9) +Part of the coordinated **`Cypress`** release ([[coordinated-releases]]; `Cypress` and `VTI-Cypress-RC-1` both point at the 0.2.0 HEAD; `Banyan` = `RC-0` = 0.1.3). The [[verifiable-trust-infrastructure|VTI]] (#916) and [[openvtc]] (#205) moved onto 0.2 the same day — VMC/VEC/VIC bytes were unchanged, so the bump was painless for them. - **`taskContext` added to `DTGCommon`** (+ accessors). This was a real bug, not just a schema catch-up: `DTGCommon` lacked the field and had no `deny_unknown_fields`, so serde silently *dropped* `taskContext` on deserialise — and because `sign()` serialises the credential, issuers signed a document missing the field while verifiers hashed a different document than the one that was signed. - **BREAKING**: `new_vwc(issuer, subject, valid_from, valid_until, task_context: String, digest: Option, witness_context)`; deserialising a `WitnessCredential` without `taskContext` fails with the new `DTGCredentialError::MissingTaskContext`; new `Canonicalization(String)` error. -- New VWC digest helpers `digest_multibase()` / `verify_digest()` — the digest covers the referenced VRC exactly as it stands, including its `proof`, over its JCS canonical form. +- New VWC digest helpers `digest_multibase()` / `verify_digest()` — at this version the digest covered the referenced VRC *including* its `proof`, over its JCS canonical form. - `DTGCredentialType::RCard`, `CredentialSubject::RCard`, `CredentialSubjectRCard` and `new_rcard()` **deprecated** (not removed) — the relationship card left the spec for a planned VDS companion. -- **⚠ VWC digest divergence (flagged in the README/CHANGELOG, unresolved).** WD01 says the digest MUST be encoded as `sha256:` + lowercase hex; the crate encodes it as a multibase base58btc multihash (`z…`), following the W3C `digestMultibase` convention. Same SHA-256 over the same JCS bytes — only the string encoding differs — but string comparison fails both ways, so `verify_digest()` rejects spec-conformant VWCs and conformant verifiers reject crate-produced ones. To be raised with the DTGWG. A second, undeclared gap: the crate still treats `digest` as `Option` (written against the PR #7 state); spec PR #14 two days later made it REQUIRED, so a VWC built with `digest: None` is now non-conformant. +- **VWC digest divergence, flagged in the README/CHANGELOG at the time.** WD01 said the digest MUST be `sha256:` + lowercase hex; the crate emitted a multibase base58btc multihash (`z…`). *Resolved in 0.4.0 (crate adopts hex form) and then mooted by WD02, which moved the spec to multibase — adopted in 0.7.0.* The second, undeclared gap — `digest` as `Option` although spec PR #14 made it REQUIRED — remains open as of 0.10.0 (see the note at the top of this section). - History reconstructed: 0.1.2 and 0.1.3 had been published but never recorded; the async `sign()` change actually shipped in 0.1.1 (2026-03-29); placeholder dates replaced with crates.io dates; four rustdoc bare-URL warnings fixed. ### v0.1.3 — 2026-06-07 — data-integrity 0.7 / TDK 0.7 @@ -81,4 +152,4 @@ After a quiet spring, the crate made its first semantic move in August 2026: **0 - Repository URL migration from LF-Decentralized-Trust-labs to OpenVTC - Crate publishing enablement -See also: [[dtg-credentials-overview]], [[decentralized-trust-graph]], [[verifiable-credentials]] +See also: [[dtg-credential-spec]], [[dtg-credentials-overview]], [[decentralized-trust-graph]], [[verifiable-credentials]], [[coordinated-releases]] diff --git a/content/entities/openvtc.md b/content/entities/openvtc.md index c80db7c..e34fba1 100644 --- a/content/entities/openvtc.md +++ b/content/entities/openvtc.md @@ -1,8 +1,8 @@ --- title: "OpenVTC — The Trust Community CLI" type: entity -tags: [openvtc, cli, tui, user-experience, primary, multi-community, agent-names, tsp, cypress] -date-updated: 2026-08-19 +tags: [openvtc, cli, tui, user-experience, primary, multi-community, agent-names, tsp, vetting, persona, themes, cypress, dogwood, eucalyptus] +date-updated: 2026-09-18 repo: https://github.com/OpenVTC/openvtc --- @@ -16,48 +16,78 @@ OpenVTC is the user-facing tool for participating in [[verifiable-trust-communit OpenVTC implements the [[first-person-network|First Person Protocol]] for the "Know Your Developer" use case. From a user's perspective: -1. **Set up your account** — bind the TUI to your [[verifiable-trust-agent|VTA]] (a managed one on the VTA Farm or your own — see [[vti-setup]]); since June 2026 setup mints *no* persona — personas are created from the dashboard when you first need one -2. **Create personas** — mint Persona DIDs ([[did-webvh|did:webvh]]), optionally with a human-readable **agent name** (`example.com/@alice`, shown wherever a DID would be) -3. **Connect with people** — send and accept relationship requests over [[trust-spanning-protocol|TSP]] or [[didcomm|DIDComm]] +1. **Set up your account** — bind the TUI to your [[verifiable-trust-agent|VTA]] (a managed one on the VTA Farm or your own — see [[vti-setup]]) and a Trust Context on it; since June 2026 setup mints *no* persona. Since Dogwood, setup **looks before it writes**: pointing a new install at a context that already holds an account offers to *recover* it from what the VTA holds, rather than silently minting a second set of personas beside the first +2. **Manage your own identity** — the **My Identity** pane (September 2026): mint Persona DIDs ([[did-webvh|did:webvh]], with a chosen or server-allocated path, in a VTA context of your choosing), optionally claim a human-readable **agent name** (`example.com/@alice`), and — one layer up — curate the pool of *facts* (attributes) behind those personas, the *faces* (profiles) that group them, which face each community is shown, and the record of what has actually been disclosed +3. **Connect with people** — send and accept relationship requests over [[trust-spanning-protocol|TSP]] or [[didcomm|DIDComm]]; since Dogwood a new relationship defaults to a **pairwise R-DID**, and the [[relationship-credential|VRC]] it produces is issued under that R-DID rather than the persona 4. **Build your trust network** — exchange [[relationship-credential|Relationship Credentials]], receive [[endorsement-credential|endorsements]], get [[witness-credential|witness attestations]] -5. **Participate in communities** — join VTCs by DID *or* agent name (presenting a [[invitation-credential|Verifiable Invitation Credential]] where required, or sending an open request for admin approval), and belong to **multiple communities at once**, each under its own persona; see which capabilities each community has enabled -6. **Sign your commits** — set up [[verifiable-git-infrastructure|did-git-sign]] so git commits are signed by a VTA-held key that a community's Trust Registry authorises +5. **Participate in communities** — join VTCs by DID *or* agent name, and by whichever way in the community offers: present a [[invitation-credential|Verifiable Invitation Credential]], **be vetted by existing members** (peer identity vetting, September 2026), or send an open request for admin approval. Belong to **multiple communities at once**, each under its own persona and (since September) in its own VTA sub-context; see which capabilities each community has enabled, why a membership was rejected or removed, and grant a second device access to one community only +6. **Vet others** — if a community has named you a *vetter* (a revocable `CommunityRole` credential), hand out tickets, run guided sessions against an applicant's Vetting Card, and sign Vetting Statements from the **Vetting desk** +7. **Assert personhood** — answer a community's personhood ceremony over Trust Tasks, matching a spoken eight-character code with the administrator in the room, so the [[personhood-credential|PHC]] claim lands on your [[membership-credential|VMC]] +8. **Sign your commits** — set up [[verifiable-git-infrastructure|did-git-sign]] so git commits are signed by a VTA-held key that a community's Trust Registry authorises -Behind the scenes, OpenVTC orchestrates the VTA (for key management and the credential vault), TSP/DIDComm messaging on the TDK's reliable delivery layer (for communication), and Trust-Task documents (for every protocol exchange). +Behind the scenes, OpenVTC orchestrates the VTA (for key management, the credential vault, and — increasingly — as the *system of record* for the account), TSP/DIDComm messaging on the TDK's reliable delivery layer (for communication), and Trust-Task documents carried in the binding envelope (for every protocol exchange). Since Eucalyptus-RC-0 it speaks **TSP Rev 3**, forming and persisting the §7.2.2 relationship a VTC requires before any application traffic is accepted. ## Components -The workspace is two crates (version **0.3.1** at the `Cypress` tag; v0.3.0 was the last tagged release): +The workspace is two crates. The workspace version has sat at **0.3.1** since 2026-08-16 — through `Cypress`, `VTI-Dogwood`, `VTI-Dogwood-R1` and `VTI-Eucalyptus-RC-0` — while a ~960-line `[Unreleased]` section accumulates in the CHANGELOG; v0.3.0 remains the last `vX.Y.Z` tag. Versions are now carried by the coordinated tree-named tags ([[coordinated-releases]]), not by the crate. ### openvtc -The user-facing TUI binary (formerly `openvtc-cli2`). The unsuffixed name is intentional, matching the convention used by uv, ruff, deno, and cargo. Main-menu panels: - -- **Inbox** — real-time task processing (auto-handles trust-pongs, relationship finalization, rejections; queues interactive tasks; detail views for inbound/outbound requests, VRCs, pings, informational messages) -- **Communities** — overview of memberships with favourites, a **Ctrl+K** switcher, join (`j` — by community DID or agent name, optional VIC paste), per-community **Capabilities** panel (`c`), leave/archive -- **Relationships** — list/detail/new-request views, inline alias editing, R-DID privacy toggle, trust-ping with RTT latency -- **Credentials** — Received/Issued tabs, raw VRC JSON in detail view, clipboard copy, VRC request and removal -- **VTA** — transports the VTA advertises (TSP / DIDComm / REST), persona **agent names** management (`g`), VIC list, key count, backend type -- **Settings** — inline editing, config export/import, passphrase protection management, hardware token detection, factory reset -- **Logs** — scrollable, transport-attributed, 200-entry durable activity log with copy +The user-facing TUI binary (formerly `openvtc-cli2`). The unsuffixed name is intentional, matching the convention used by uv, ruff, deno, and cargo. Main-menu panels (in menu order, as of 2026-09-18): + +- **Communities** — the landing page: memberships with favourites, a **Ctrl+K** switcher, join (`j` — by community DID or agent name; the flow asks the community what it requires and lists *every way in* — invitation, vetting, open request — saying which are open to this account and why), per-community **Capabilities** panel (`c`), leave/archive, **delete a finished community's VTA context** (`D`), **per-community device access** (`g`), each row's "presents: *face*" clause and how the membership is kept apart (own context / shared / top), and a `Reason:` block for rejected or removed memberships +- **Inbox** — real-time task processing (auto-handles trust-pongs, relationship finalization, rejections; queues interactive tasks; detail views for inbound/outbound requests, VRCs, pings, informational messages); `a` accepts a relationship request *pairwise*, `p` under the persona DID +- **My Relationships** — list/detail/new-request views, inline alias editing, the pairwise-vs-persona toggle with its consequences spelled out (seeded from the community's `relationshipIdentifierDefault`), trust-ping with RTT latency +- **My Credentials** — Received/Issued tabs, raw VRC JSON in detail view, clipboard copy, VRC request and removal +- **Vetting** (new, September 2026) — two tabs: **Applications** (be vetted: start one per community, choose the face the vetters will see, ask a vetter by ticket code, read the match code, send the card, watch the checklist fill against the community's published requirements, take up the join from the application) and the **Vetting desk** (vet others: headed by the `CommunityRole: vetter` credential that names you, with Requests / Tickets / Issued sub-views, guided session checks, attest or decline, withdraw a statement, QR tickets, your vetter profile) +- **My Identity** (new, September 2026) — five tabs: **Personas** (mint with a chosen DID path and context; agent names), **Your facts** (the attribute pool; values fetched only on request; sensitive facts masked and fetched one at a time), **Faces** (profiles over the pool, with a cursor to reveal a claim), **Communities** (which face each community is shown), **What has left** (the disclosure record). Personas used to live on the VTA panel; "identity is not a property of the agent that hosts its keys" +- **Settings** — inline editing, config export/import, passphrase protection management, hardware token detection, **Theme** picker with live preview, factory reset (naming the `pnm contexts delete ` to run); mediator DID is read-only until a persona exists +- **VTA Service** — "about the agent again": transports the VTA advertises (TSP / DIDComm / REST), the VIC list, key count, backend type +- **TSP Relationships** (new, Eucalyptus-RC-0) — each joined community's TSP Rev 3 §7.2.2 relationship state (None / Pending / Invite received / Bidirectional); a different keyspace, lifecycle and wire from the DIDComm relationships model +- **Logs** — scrollable, transport-attributed, 200-entry durable activity log with copy; listener lines carry the listener id and the speaking persona, and a mediator's scheduled reconnect is one calm line - **Help/Status**, **Quit** -It also has a non-TUI subcommand, **`openvtc health [--vtc ] [--json]`** (0.3.1), which probes the VTA, mediator, DID hosting, and optionally a community, with graded results. +Non-TUI surface: **`openvtc setup`**; **`openvtc health [--vtc ] [--json] [--recoverable] [--allow-private-probes]`** — reports *local storage first* (which credential store, whether this profile's secret is there), then the DID this install authenticates to the VTA as (with the `pnm acl` commands ready to run), each community's TSP relationship state, and graded transport probes that dial public HTTPS only and never follow a redirect; **`openvtc theme list|set|import|export`** (see Configuration below); `--unlock-code-file ` replaces the deprecated `--unlock-code` in argv. A `dev-overrides` cargo feature is the *only* build that honours `OPENVTC_VTA_URL` / `OPENVTC_VTA_DID` / `OPENVTC_MEDIATOR_DID`; release builds ignore them and say so. ### openvtc-core -Shared library (`publish = false`): config (v2 `Account` / `PersonaRecord` / `CommunityRecord` / `IdentityRegistry`), BIP-32 key derivation, relationship state machines, `messaging.rs` (a pure protocol state machine), `didcomm.rs` (the transport, built on `affinidi-messaging-delivery`, with stored-mail pickup), `tsp.rs`, `agent_name.rs`, `capabilities.rs`, `presentation.rs` (DCQL → consent summary → `vp_token`), `join.rs`, `health.rs`, and OpenPGP card support. MSRV 1.95, edition 2024. +Shared library (`publish = false`): config (v2 `Account` / `PersonaRecord` / `CommunityRecord` / `IdentityRegistry`, each with a forward-compatible `extra` catch-all and a `LoadIntegrity` report), BIP-32 key derivation, relationship state machines, `messaging.rs` (a pure protocol state machine), `didcomm.rs` (the transport, built on `affinidi-messaging-delivery`, with stored-mail pickup), `tsp.rs` + `tsp_store.rs` (TSP Rev 3 send path and the durable §7.2.2 relationship store), `trust_task_doc.rs` (the one place a Trust Task document is built), `agent_name.rs`, `capabilities.rs`, `presentation.rs` (DCQL → consent summary → `vp_token`), `join.rs`, `members.rs`, `vrc.rs`, `personhood.rs`, `vetting/` (tickets, applicant, vetter, wire, registry, queries), `persona/` (pool, profile, binding, facet, family, claim types, disclosure, correlation), `rebuild.rs` / `rebuild_apply.rs` / `context_probe.rs` / `credential_sync.rs` (recovery from the VTA), `devices.rs`, `community_access.rs`, `secure_store/` + `diagnostics.rs`, `health.rs` + `net_guard.rs`, `process_lock.rs`, and OpenPGP card support. MSRV 1.95, edition 2024. ### Crates that have left the workspace -- **`did-git-sign`** — the git/SSH signing helper that uses the VTA as a signing oracle was developed and dogfooded here (auto-configured by the setup wizard since v0.1.6; per-repo persona selection since T8) and then **extracted to its own repo, [[verifiable-git-infrastructure|Verifiable Git Infrastructure (VGI)]], in July 2026** (#159). OpenVTC now consumes the published crate (0.4.5 at Cypress) only for `init::install/uninstall` and signing config, and pins VGI's `verify-trust` GitHub Action for its own CI. A standing obligation recorded in `Cargo.toml`: VGI's `vta-sdk` line must track OpenVTC's, or two `vta-sdk` copies land in the binary. +- **`did-git-sign`** — the git/SSH signing helper that uses the VTA as a signing oracle was developed and dogfooded here (auto-configured by the setup wizard since v0.1.6; per-repo persona selection since T8) and then **extracted to its own repo, [[verifiable-git-infrastructure|Verifiable Git Infrastructure (VGI)]], in July 2026** (#159). OpenVTC consumes the published crate only for `init::install/uninstall` and signing config — 0.4.5 at Cypress, 0.4.6 at Dogwood (via a git pin on VGI while VGI caught up with vta-sdk), **0.4.12 at Eucalyptus-RC-0** — and pins VGI's `verify-trust` GitHub Action (v0.4.9, by commit SHA, #311) for its own CI. The standing obligation recorded in `Cargo.toml` — VGI's `vta-sdk` line must track OpenVTC's or two `vta-sdk` copies land in the binary — was finally *settled* rather than patched on 2026-09-13 (#321): one `vta-sdk`, one `didwebvh-rs`, no git sources. Note that installing did-git-sign from the setup wizard has been unreachable since the R-A-5 setup split; the main page still detects an existing config, but re-offering the install needs a new entry point (`tasks/follow-ups.md`). - **`openvtc-service`** (background DIDComm daemon) and **`robotic-maintainers`** (auto-accept test service) were removed in v0.2.1 (#63); the daemon's role is covered by the TUI's own messaging runtime, and the test-fixture role moved to the in-tree mediator harness. **`openvtc-cli`** (legacy prompt-driven binary) was deleted in v0.2.0. +### Dependency snapshot + +OpenVTC functions as the reference client for the VTI stack, so its dependency lines are the clearest record of what each coordinated tag actually means. All values are the resolved `Cargo.lock` versions (the manifest floors are the same or one patch lower): + +| Crate | `Cypress` 08-17 | `VTI-Dogwood-RC-1` 08-29 | `VTI-Dogwood` 08-30 | `VTI-Dogwood-R1` 08-31 | `VTI-Eucalyptus-RC-0` 09-17 | HEAD 09-18 | +|---|---|---|---|---|---|---| +| workspace (`openvtc`, `openvtc-core`) | 0.3.1 | 0.3.1 | 0.3.1 | 0.3.1 | 0.3.1 | 0.3.1 | +| `vta-sdk` | 0.25.0 | 0.32.1 | 0.32.2 | 0.32.2 | **0.42.1** | 0.42.1 | +| `trust-tasks-rs` / `-capability-client` | 0.9 / 0.8 | 0.17 / 0.17 | 0.17 / 0.17 | 0.17 / 0.17 | 0.21.3 / 0.21.0 | same | +| `dtg-credentials` ([[dtg-credentials]]) | 0.2.0 | 0.3.0 | 0.5.0 | 0.5.0 | **0.9.1** (WD-02) | 0.9.1 | +| `affinidi-tdk` ([[affinidi-tdk]]) | 0.8 | 0.10 | 0.10 | 0.10 | 0.16 | 0.16 | +| `affinidi-messaging-sdk` / `-delivery` / `-core` | 0.19.4 / 0.1.12 / 0.1.6 | 0.21 / 0.1.14 / 0.1.6 | same | same | 0.26.7 / 0.1.15 / 0.1.8 | same | +| `affinidi-tsp` (transitive) | 0.1.14 | 0.1.14 | 0.1.14 | 0.1.14 | **0.2.1** (Rev 3) | 0.2.1 | +| `affinidi-did-resolver-cache-sdk` / `didwebvh-rs` | 0.8.19 | 0.8.34 | 0.8.34 | 0.8.34 | 0.8.37 / 0.7 | same | +| `affinidi-did-web` / `affinidi-net-guard` | — | — | — | — | 0.1.5 / 0.1.0 | same | +| `agent-names` | 0.1.2 | 0.1.3 | 0.1.3 | 0.1.3 | 0.1.3 | 0.1.3 | +| `did-git-sign` ([[verifiable-git-infrastructure]]) | 0.4.5 | 0.4.6 (git pin) | 0.4.6 (git pin) | 0.4.6 (git pin) | 0.4.12 | 0.4.12 | +| `vta-service` / `affinidi-messaging-test-mediator` (dev) | 0.17 / 0.2 | 0.23 / 0.4 | 0.23 / 0.4 | 0.23 / 0.4 | 0.32 / 0.9 | same | +| `[patch.crates-io]` entries | 0 | VGI git pin | VGI git pin | VGI git pin | 0 | 0 | + +`vta-sdk` features at HEAD: `session, client, keyring, didcomm, provision-client, vetting, tsp, vp`. Two pins are explicitly *coupled* to `vta-sdk` in the manifest's comments — `trust-tasks-rs` (re-exported types cannot unify across two copies) and, since 0.42, `dtg-credentials` — and `cargo tree -d` on each is the check. + ## Identity Model -OpenVTC uses a two-layer identity model: +OpenVTC's identity model has three layers, the third added in September 2026: + +- **Persona / Membership DIDs** — your public identities, one per community membership, created as [[did-webvh|did:webvh]] hosted on the domain of your choice (via the VTA's configured DID host), with a server-allocated mnemonic path or one you type (#324), and optionally claimed by an **agent name** in `alsoKnownAs`. Since #298/#301 each persona's keys and DID are minted **in a VTA sub-context** — by default one per community, so communities no longer share one key hierarchy and are isolated in more than name. A membership row says when the same persona is shown to other communities, because that linkage is the one thing a holder cannot work out from a single row. +- **Relationship DIDs (R-DIDs)** — private `did:peer` identifiers, one per relationship. Since Dogwood (#254/#255) pairwise is the **default**, not the opt-in, and the VRC is issued *under the R-DID* — the durable artefact both parties keep is the worst place to leak a persona. The three handshake messages still route persona-to-persona (the mediator has to route them before a pairwise channel exists); full pairwise operation is a protocol change tracked as VTI #1054. A community may declare `relationshipIdentifierDefault: attributed`, which seeds the form but never gates it (#335). +- **Facts, faces, and what a persona wears** — the VTA's `persona/*` family, one layer above the DID: an *attribute* is a **fact**, a *profile* is a **face** (a named subset of facts), and a persona **wears** a face in a community (`persona/binding`). The vocabulary is fixed across the console, `pnm`, the mobile agent and this TUI by `design-docs/persona-vocabulary.md`; the spec's words stay on the wire and off the screen. Reaching this layer needs the `persona-holder` capability (`pnm … --admin-holder` at setup), which grants authority over your own identity without making OpenVTC an administrator of every context (VTI #1286). -- **Persona / Membership DIDs** — your public identities, one per community membership, created as [[did-webvh|did:webvh]] hosted on the domain of your choice (via the VTA's configured DID host) and optionally claimed by an **agent name** in `alsoKnownAs` -- **Relationship DIDs (R-DIDs)** — private `did:peer` identifiers, one per relationship, so your persona DID isn't exposed in every interaction +A name is only ever *displayed* if it forward-resolves and round-trips back to the labelled DID (user alias → verified agent name → truncated DID); the DID, never the name, is what gets persisted. Sensitive facts are masked by the claim-type registry the agent serves (`persona/claim-types/list`) and fetched one at a time on `s` — bulk reads never carry a card number into the process (#340). -A name is only ever *displayed* if it forward-resolves and round-trips back to the labelled DID (user alias → verified agent name → truncated DID); the DID, never the name, is what gets persisted. +**TSP Rev 3 relationships** are a fourth, separate keyspace (Eucalyptus-RC-0, #333): the §7.2.2 relationship this account has *formed* with each community VTC, without which the VTC silently drops application traffic. They are persisted in `ProtectedConfig` so a restart does not forfeit them. ## Configuration @@ -76,9 +106,55 @@ Protection modes: Multiple profiles are supported via the `OPENVTC_CONFIG_PROFILE` environment variable or `-p` flag. +**Where the secrets live (Dogwood, #246).** The OS credential store is now chosen by `vta_sdk::keyring_init::install_default_store` — the same call `pnm-cli` makes — so `openvtc` and `pnm` keep secrets in the same place: Apple Keychain, Windows Credential Manager, or DBus Secret Service. On Linux OpenVTC had been the only tool using the kernel `keyutils` keyring, which is RAM-only and had been quietly destroying accounts on reboot. If the store cannot be opened OpenVTC **fails closed** rather than downgrading; headless machines opt into an encrypted file store with `OPENVTC_SECURE_STORE=file` (`~/.config/openvtc/secrets/`, mode 0600, refuses to hold an unencrypted profile). The secured envelope is written as one line, because gnome-keyring corrupted its whole collection on a pretty-printed one (#269). `ProtectedConfig` is now encrypted under a **random per-profile key** held in `SecuredConfig`, not under a derivation of the admin credential (D12) — so the credential can rotate or be re-issued to a recovering install without making local state undecryptable. Startup failures each get their own diagnosis (`~/.config/openvtc/last-startup-failure.txt`). + +**The VTA as system of record.** `docs/design/vta-authoritative-state.md` (DRAFT v6) sets the direction: keys, identities and credentials live at the VTA; local config is a cache that can be rebuilt. The rebuild path (#248–#251) reconstructs personas (re-establishing DID ↔ key correspondence with the VTA's own `select_secret_kid` rule) and memberships (from VMCs now stored in the VTA credential vault via `vault/credentials/receive`), and setup offers it when a context is already occupied. What is still missing is an application-state store on the VTA (ask E2, *blocking*) for the metadata that is neither key nor credential. + +**Themes (`tui.toml`).** Since #299/#300 the TUI is drawn in any theme: built-in (including High Contrast and Colourblind Safe pairs measured against WCAG), your own under `~/.config/openvtc/themes`, Omarchy's (followed live), or imported from base16/base24, Alacritty, Kitty, Ghostty, or any Neovim colorscheme. `auto` follows the terminal background (OSC 11 → `COLORFGBG` → dark); `NO_COLOR` is honoured; `OPENVTC_THEME` overrides for one session; `openvtc theme export` writes a theme back out for other tools. The choice belongs to you rather than to a profile (a per-profile remembered choice was scoped in #336). + ## Recent Development -The focus has moved from security correctness (v0.1.x), through feature completeness (v0.2.0), through the multi-community pivot (`Banyan`, June 2026), to **making the multi-community client reliable and humane**: the July–August cycle (83 commits, PRs #152–#237) shipped **v0.3.0** — the join ceremony's asynchronous half — plus **agent names**, TSP as a live transport, a per-community Capabilities panel, the reliable delivery layer underneath, and the extraction of did-git-sign into VGI; it ends in the coordinated **`Cypress`** release ([[coordinated-releases]]). +The focus has moved from security correctness (v0.1.x), through feature completeness (v0.2.0), through the multi-community pivot (`Banyan`, June 2026) and making that client reliable (v0.3.0 / `Cypress`, August), to **two things at once**: making the VTA the *system of record* so an install is recoverable, and giving the client the identity and admission machinery — facts and faces, per-community contexts, peer vetting, personhood — that the ecosystem's specs had been describing but nothing user-facing implemented. The month since Cypress (103 commits, PRs #238–#343) passed through the deliberately quiet **`VTI-Dogwood`** (2026-08-30), its re-cut **`VTI-Dogwood-R1`** (08-31), and into **`VTI-Eucalyptus-RC-0`** (2026-09-17) ([[coordinated-releases]]). + +### Dogwood (2026-08-30) → Dogwood-R1 (08-31) → Eucalyptus-RC-0 (2026-09-17): the VTA as system of record, vetted admission, and the identity pane + +The workspace stays at 0.3.1 throughout — no `vX.Y.Z` tag, no GitHub Release for Dogwood, ~960 lines of `[Unreleased]` CHANGELOG. Two design specs frame the window. **`docs/design/vta-authoritative-state.md`** (DRAFT v6) starts from *"My laptop died. I install OpenVTC on a new machine, point it at my VTA and my Trust Context, and it offers to recover"* — and finds that the admin credential was doing two irreconcilable jobs, that concurrent instances break the messaging layer *today*, and that the VTA needs an application-state store (E2, blocking, still open). **`docs/design/vetting-process.md`** (DRAFT v3, V0 target 5 Oct 2026) replaces the Linux kernel maintainers' PGP web of trust with peer identity vetting: an applicant builds a signed **Vetting Card** from a persona face, presents it to several **vetters** who check it against a passport in person or on video and issue **Vetting Statements**, and submits them to the VTC, which admits under policy. Most of that protocol lands in the ToIP Trust Task specs and VTI; OpenVTC is where both halves — applicant and vetter — become a screen. + +#### Dogwood — recoverable installs, honest storage, and the membership pair finally landing (#238–#272) + +*Cypress..VTI-Dogwood: 41 commits, 80 files, +18.6k / −2.6k.* A "silent" release by design: almost everything here is a bug that had been making the client lie, and the fix is that it now tells the truth. + +- **Storage that survives a reboot** (#246). On Linux OpenVTC had registered the kernel `keyutils` keyring — documented as RAM-only — as the *sole* home of a profile's seed and credential bundle; a reboot, a logout, or three days of the persistent keyring's default expiry destroyed the account, surfacing as `Couldn't find openvtc secured configuration`. Store selection now delegates to the SDK's `install_default_store`, fails closed instead of downgrading, and offers `OPENVTC_SECURE_STORE=file` for headless machines. In the same PR: `Config::save` writes secrets *before* the config file so an interrupted save leaves a persona absent rather than half-written; a half-written persona no longer fails the whole load (`LoadIntegrity`, acknowledged on the startup screen, "nothing has been deleted"); every startup failure gets its own diagnosis instead of "check your network"; and `openvtc health` reports local storage first, even when no account loads. +- **Break the credential ⇄ encryption coupling (D12).** `ProtectedConfig` was keyed on the admin credential's private key — an *authorisation grant* meant to rotate, doing duty as a *data-at-rest key* that must not. The profile now carries its own random key; load tries three keys newest-first and re-keys on the next save, crash-safely. Config records gain a flattened `extra` so an older build round-trips a newer one's fields (D19). +- **Setup looks before it writes; recovery is a branch in setup** (#247–#251). Pointing a fresh install at an occupied Trust Context used to mint a second set of personas beside the first. Setup now probes (personas, sub-contexts — counted, never gestured at; "could not tell" kept distinct from "empty"), offers *use a different context* first, *continue* on a deliberate `C`, and never offers delete. `openvtc_core::rebuild` reconstructs an account from what the VTA holds: DID ↔ key correspondence via the VTA's own `select_secret_kid`; memberships rebuilt Active from their VMCs, each carrying its own proof. Running it live found that VMCs had never been stored at the VTA at all, that `cred_vault_query({})` is refused by design (the vault is non-enumerable) and both callers had read the refusal as "zero", and that `query` returns body-free descriptors — so a `credential_sync` on connect now pushes every membership credential through `vault/credentials/receive` (#251). +- **Privacy defaults flipped** (#254, #255 — *breaking on the wire*). A new relationship mints a pairwise R-DID unless turned off; `a` accepts pairwise, `p` under the persona. Then the half that mattered: the VRC is **issued under the R-DID**, signed with its key — previously `issuer = persona_did`, so the durable, publishable credential led straight back to the persona. This needed VTI #1061 to stop pinning a published VRC's issuer to the session DID; `vet_vrc_issued` gate 2 is now tighter *and* more private (issuer must be the sender's DID in *this* relationship). A pasted VIC must now carry DTG's common structure — the `firstperson.network/credentials/dtg/v1` context and `DTGCredential` type — or it is refused at ingest (#256, breaking; the fixtures had encoded the implementation's belief rather than the spec's). +- **Personhood over Trust Tasks** (#257). The VTC's `challenge`/`assert` verbs were REST-only; a client that talks DIDComm/TSP and holds no bearer token could not take part. Both now ride the Trust Task document path. `match_code` derives eight Crockford-base32 characters (no I/L/O/U) from the challenge UUID — the thing two people in a room actually say to each other; `5CY1-GZEE` is pinned on both sides so a drift reads as "software disagrees" rather than "wrong ceremony". The challenge is written twice, to `proof.challenge` (what the spec names) and top-level `nonce` (what the Data Integrity signature actually covers). +- **The membership pair had never once landed** (#259–#267). Every reciprocal VMC OpenVTC sent a community had been rejected — `dtg-credentials` 0.2 had *no field* for the W3C credential `id` the VTC keys on — and it was silent from both ends: the send reported success on a local `Ok`, and the rejection problem-report was threaded on a VMC delivery, so the join handler dropped it as "uncorrelated". dtg-credentials 0.3 adds `with_id()` (#260); VRCs get a `urn:uuid:` id too (#265); an uncorrelated problem-report is now surfaced with its code (#267); and receiving the community's VMC **auto-issues ours back, closing the join request** by its id (#266 — behaviour change; a join used to sit `Approved` at the VTC forever). Around it: a removed listener's socket was never closed, so re-adding a DID fought its own orphan indefinitely (#259); every launch after the first warned that the account was "open elsewhere" — the device listing could not recognise its own binding (#261, fixed by `consumerDid`); a renamed machine's heartbeat carries its name (#264); the mediator's scheduled token-refresh reconnect is one calm line instead of an alarm every twelve minutes (#262). +- **Setup could not link to a VTA at all** — `provision/integration` moved to 0.3 upstream and only the SDK's REST runner followed; setup prefers TSP, and the post-auth refusal was classified terminal so it never fell back. Floored at vta-sdk 0.32.1 (#268), then 0.32.2 (#271). The grant is acknowledged by digest and the credential we sent is kept (#272, dtg-credentials 0.5). Also: connection status seeded from the live listener rather than an edge, so a first-run join no longer shows "Connecting…" for the life of the process (#270); the R14 perf batch finished — every remaining network action (capabilities, leave, VMC issue, persona create, VRC request) off the state-handler loop (#239–#244), and the 1,600-line action `match` lifted into a testable `runtime_actions::handle_action` (#245). +- **Dependencies at Dogwood:** trust-tasks 0.9 → 0.17, affinidi-tdk 0.8 → 0.10, vta-sdk 0.25 → 0.32.2, messaging-sdk 0.19.4 → 0.21, dtg-credentials 0.2 → 0.5, did-git-sign 0.4.6 via a git pin on VGI (#252, #253, #258, #263). + +#### Dogwood-R1 (2026-08-31) — the re-cut + +Two commits, seven files, +343 / −100. **#273** is the one that mattered: a join to a community whose mediator is not ours failed at the first hop with `TSP recipient is not local to this mediator` — from *our* mediator, because the routed send had been passing the peer's mediator as `route[0]` when only our own can unwrap the outer layer. Invisible in a single-mediator deployment; broken in the ordinary federated case. The route is now built outward, `[ours, theirs, recipient]`. **#274** makes the mediator DID read-only in Settings on a profile with no persona and stops reporting saves that did not happen. + +#### Toward Eucalyptus — My Identity, contexts, vetting, themes, hardening (#275–#333) + +*VTI-Dogwood-R1..VTI-Eucalyptus-RC-0: 56 commits, 116 files, +49.1k / −1.9k — the largest window in the project's history.* + +- **The identity pane** (#275–#290, #324, #340). It began by asking the agent which face a persona wears in each community (`persona/binding/get`, #275) — with "we could not ask" carried as a field, never as an absent `Option` that renders as "presents nothing" — and putting *presents: work (3 claims)* on every membership row (#276). Then **My Identity** (#277): personas, facts, faces, communities, disclosures in one pane, with values opt-in and fetched only on `v`, destructive prompts naming the real cascade, and credential-backed attributes shown but not retyped. Setup asks for the `persona-holder` grant (#279). The pane speaks the persona vocabulary (#280/#281), with a test that fails if any avoid-list word reaches the screen. Sensitive facts are masked by claim type (#282), first from a vendored table and then from the registry the agent serves, since a deployment may declare its own types (#290, with worlds and families). A face gets a cursor so a single claim can be read (#288). The DID path can be chosen (#324). `include_sensitive` becomes a real second escalation: bulk reads never fetch sensitive values; `s` fetches one (#340). +- **Each community in a VTA context of its own** (#298, #301). OpenVTC had *derived* a sub-context id per membership but never created it, minting every persona in the top context — isolated in name only. The join now asks where the community lives (own sub-context by default), creates it, and mints the persona's keys there; a finished membership can delete its context (with the VTA's `preview-delete` cascade shown and `DELETE` typed); and **per-community device access** grants a second device's `did:key` the least-privileged `application` role scoped to that context alone (1/7/30/90 days). +- **Vetted admission, V0** (#292–#297, #307, #319, #322, #325, #337). The design doc (#292); `openvtc_core::vetting` — tickets as the anti-spam gate (a wrong code gets no answer), applicant and vetter state, signed peer documents bound to the authenticated sender (#294); the **Vetting page** (#295, with a vetting community's `decisionSla` replacing the 7-day pending timeout); the card read from the persona's *face* through persona disclosure, so the community sees what the vetters checked (#296); **vetters named by a revocable `CommunityRole` credential** the community grants, presented as `eligibilityVp` on every acceptance (#297); the vetter directory, profile, QR tickets, grant status-list checks, resend and community branding (#307); the wire types swapped for the *generated* `trust_tasks_rs::specs` ones, which validate what the hand-written ones accepted (#319); the ceremony end to end over a real mediator (#322); the desk headed by the credential that makes you a vetter — the only view that keeps a *lapsed* grant, so a lapse is no longer invisible (#325). +- **Themes** (#299, #300): seven colour roles swapped per frame, so no panel changed; built-in, own, Omarchy, and imported themes; live switching within a second; `auto` light/dark; accessible themes measured for WCAG contrast; `NO_COLOR`; export. `docs/themes.md`. +- **Security (SEC-4045 and around it)**. `openvtc health` had dialled every transport URL in any DID document anyone could publish — any host, system proxy, ten redirects. It now probes public HTTPS only, no redirects, through a guarded DNS resolver (#304), and since #312 the *resolver* refuses `did:webvh` on localhost/private/link-local hosts too (`--allow-private-probes` opts both in together); regression gates prove no connection opens (#320). Release builds ignore `OPENVTC_VTA_URL` / `_VTA_DID` / `_MEDIATOR_DID` — an override set for one launch used to be written into the profile by the next save (#305; `dev-overrides` feature). The debug log is created 0600, never through a symlink, and defaults to a filter that does not include every dependency; `--unlock-code-file` keeps the passphrase out of argv; no factory-PIN literal in the binary (#309). CI actions pinned to commit SHAs, `permissions: contents: read`, Dependabot (#310). rustls 0.23.45 for RUSTSEC-2026-0285 (#323). +- **The dependency unwind** (#286, #291, #293, #306, #316–#319, #321, #332). The window opened with twenty VTI crates and two VGI ones under `[patch.crates-io]`, waiting on the `persona/facet/*` tasks and a vetting `feature`; it closed on 2026-09-10 (#291, vta-sdk 0.35, patch block *deleted* and `deny.toml`'s `allow-git` with it, "so reintroducing one is a visible decision"). Then 0.36, 0.37 (generated vetting types), 0.38 with did-git-sign 0.4.10 (#321 — the last duplicate sdk and the second `didwebvh-rs` gone), 0.39, and 0.42.1. The constraint that outlived the merge: vta-sdk ≥ 0.35 refuses an *unsigned* trust-task reply by default, so any VTA this build talks to must be vta-service 0.25+. +- **Trust Tasks in the binding envelope, both directions** (#327–#331). `OPENVTC_CATCH_ALL_PATTERN` listed task URIs only, so the binding envelope replies the VTC sent to capability writes were dropped between a sender and a handler that both existed (#328). Four sends had typed their DIDComm message with the task URI instead of the envelope, and two (`members/self-remove`, `members/vmc`) sent bare payloads that bypassed the VTC's signing spine (#331). One `trust_task_doc::build_value` replaces three builders (#330); a census test fails on a forgotten routing prefix (#329). +- **TSP Rev 3 as a first-class citizen** (#333 — the Eucalyptus-RC-0 commit). Rev 3 §7.2.2 gates application traffic on a *recorded relationship*: a peer the endpoint holds none with has its messages silently dropped. OpenVTC now forms the relationship (`form_relationship_routed`, idempotent) before any VTC Trust Task and after `enable_tsp_trust_tasks` on the VTA leg; recognises inbound `RelationshipControl`; and persists relationships in a config-backed `PersistentRelationshipStore` mirrored into `ProtectedConfig`. A **TSP Relationships** pane and a `health` finding name the "no relationship → dropped" case that explains a join to a healthy VTC that gets no reply. The same commit takes the Rev 3 frontier — vta-sdk 0.42.1, affinidi-tdk 0.16, messaging-sdk 0.26.7, affinidi-tsp 0.2.1, trust-tasks 0.21.3, **dtg-credentials 0.9.1** (Working Draft 02: the member VMC's grant binding moves from `sha256:` `digest` to base58btc `digestMultibase` — a wire change the whole stack flips together; and vta-sdk 0.42 itself now depends on dtg-credentials, so the pin follows the sdk). + +#### After RC-0 (2026-09-17/18, #334–#343) + +Why a membership ended is now *persisted* — code, reason, authority, time from the `deny` verdict, the FORBIDDEN problem-report, the join-denial error, or the status poll — and shown as a `Reason:` row; `vtc/members/removal-notice/0.1` is the first path that moves a membership to `Removed` (#334, closes #240). A community's `relationshipIdentifierDefault` is read via `vtc/community/profile/show/0.1` and seeds the relationship form (#335, closes #241; explained on the form, #338). An oversized join submit fails loudly instead of being silently dropped by a bridge size limit — the root cause behind PR #137, deferred since (#339). Joining a community that vets **offers every way in at once** — invitation, vetting, open request — each marked available or not and why, and `j` on an application takes up the join it was made for, so vetting reads as a *way of joining* rather than something to do before it (#342, #343). A first join from an account with no inbound arm now says the community could not be asked rather than falling through to an open request in silence. The Vetting page guides to creating a persona in-panel and brings a new one online at runtime (#337). + +**Still open** (`tasks/follow-ups.md`): D4 (VP construction — the vetting spec supplies the requirement model it lacked); persona key rotation; consuming `vtc-client` for the join ceremony and deleting the hand-built `join.rs`/`tsp.rs` path (VTI #1399 fixed the blockers, waiting on a release); verifying VTC trust-task *reply* proofs (waiting on a `vtc-service` release past 0.11.58); the identity pane's four deliberately-omitted `persona/*` verbs; the vetting spec's V0 (5 Oct). ### v0.3.0 — 2026-08-15 — the join ceremony's asynchronous half; `Cypress` — 2026-08-17 diff --git a/content/entities/rp-sdk-js.md b/content/entities/rp-sdk-js.md index 8e69220..3d9c7fa 100644 --- a/content/entities/rp-sdk-js.md +++ b/content/entities/rp-sdk-js.md @@ -1,8 +1,8 @@ --- title: "rp-sdk-js — Relying Party SDK for VTA logins" type: entity -tags: [relying-party, sdk, siopv2, login, javascript, secondary] -date-updated: 2026-08-19 +tags: [relying-party, sdk, siopv2, login, javascript, security, secondary] +date-updated: 2026-09-18 repo: https://github.com/OpenVTC/rp-sdk-js --- @@ -16,15 +16,22 @@ A server-side, framework-agnostic TypeScript SDK for **relying parties** (websit `verifyIdToken({ idToken, audience, nonce, resolver })` pins `alg` to EdDSA, enforces the SIOPv2 rule `iss === sub`, requires an exact `aud` (the RP's DID), does a constant-time nonce match, checks `iat`/`exp` within a skew window, resolves the issuer DID and verifies the JWS against its Ed25519 authentication key — with a typed `IdTokenVerificationError.reason`. A bundled `KeyResolver` handles `did:key` in-process; a `DidResolver` interface covers did:peer:2 / did:webvh / did:web (e.g. by wrapping the TDK's resolver cache SDK). `establishSession` returns an HttpOnly / Secure / SameSite=Strict cookie descriptor. -Since #4 (2026-07-06, unreleased): the RP side of the `confirm/{request,response}/0.1` Trust-Task consent protocol — build/sign a confirm request, verify the holder's response (`eddsa-jcs-2022` Data Integrity proof, `subject === issuer === signer`, challenge echo) — plus `jcsCanonicalize` (RFC 8785) with a cross-implementation fixture signed by pnm-core. +Since #4 (2026-07-06) the RP side of the `confirm/{request,response}/0.1` Trust-Task consent protocol — build/sign a confirm request, verify the holder's response (`eddsa-jcs-2022` Data Integrity proof, `subject === issuer === signer`, challenge echo) — plus `jcsCanonicalize` (RFC 8785) with a cross-implementation fixture signed by pnm-core. Since #6 (2026-09-12) that verifier is hardened: passing `audience` now *requires* a matching `recipient` (a response bound to no RP could be re-presented to another), `expiresAt` and an optional `maxAgeSecs` over `issuedAt` are enforced after the proof verifies, the challenge echo is compared in constant time, and canonicalization is bounded (`JCS_MAX_DEPTH` 100 / `JCS_MAX_BYTES` 1 MiB) so an attacker-shaped document surfaces as a typed `document_too_complex` rather than a stack overflow in the RP's request handler. ## Where It Fits -Both wallet login shapes produce the same SIOPv2 `id_token` and verify identically: the **self-issued** path (holder did:key) and the **VTA-proxied** path (`vault/proxy-login`, a VTA-held key — typically a did:webvh, so a custom resolver is needed). [[affinidi-webvh-service|did-hosting-service]]'s own M2B.4 login demo does this verification in Rust inside its control plane; rp-sdk is for *third-party* RPs outside the VTI stack. +All three wallet login shapes produce something this SDK verifies by resolving a DID: the **self-issued** SIOPv2 path and the **VTA-proxied** path (`vault/proxy-login`) both yield the same `id_token`, and the Trust-Task consent path yields a `confirm/response`. Note that since `VTI-Dogwood` the wallet signs in as a **per-site persona** by default — a vault-held, VTA-minted DID (typically did:webvh) rather than the wallet's did:key holder — so a production RP needs a custom `DidResolver`, not just the bundled `KeyResolver`. [[affinidi-webvh-service|did-hosting-service]]'s own login demo does this verification in Rust inside its control plane; rp-sdk is for *third-party* RPs outside the VTI stack. + +## Versions at the coordinated releases + +| Cypress | VTI-Dogwood | VTI-Dogwood-R1 | main (2026-09-18) | +|---------|-------------|----------------|-------------------| +| 0.2.0 | 0.2.0 | 0.2.0 (same commit) | 0.2.0 (unreleased changes) | ## Recent Development -- 0.1.0 (2026-05-24), 0.1.1 (05-28, metadata), **0.2.0 (06-07)** — dependency advisories, noble v2, removed a phantom `./express` export. #4 (07-06) added the confirm protocol (unreleased). Quiet since July. -- Roadmap: `requireStepUp()` (acr = aal2), `refreshProxy()`, Express / Fastify / Hono adapters, DIDComm packing helpers. +- **Dogwood and after (2026-08-30 → 09-12; #5, #6)** — two commits, one of them consequential. `VTI-Dogwood` and `-R1` tag #5 (08-30), a dev-dependency refresh (nanoid, postcss); Dogwood-RC-1 pointed at the same commit as Cypress. Then **#6 (09-12), part of the cross-repo `sec-4045` egress/input-hardening review** that also touched [[vti-didcomm-js]] and the browser plugin: four fixes to `verifyConfirmResponse` (bounded JCS, mandatory audience binding when `audience` is passed, expiry / max-age checks, constant-time challenge compare — see above). All additive for existing callers except that a caller passing `audience` against a response with no `recipient` now fails, which is the point. 55 tests. **Nothing since 0.2.0 has been published to npm**: the confirm protocol (#4) and its hardening (#6) both sit under "Unreleased" in the CHANGELOG. +- 0.1.0 (2026-05-24), 0.1.1 (05-28, metadata), **0.2.0 (06-07)** — dependency advisories, noble v2, removed a phantom `./express` export. #4 (07-06) added the confirm protocol. +- Roadmap: a 0.3.0 release carrying the confirm protocol; `requireStepUp()` (acr = aal2), `refreshProxy()`, Express / Fastify / Hono adapters, DIDComm packing helpers. `VTI-Eucalyptus-RC-0` (2026-09-17) is not yet tagged here. -See also: [[vta-browser-plugin]], [[vti-didcomm-js]], [[verifiable-trust-agent]] +See also: [[vta-browser-plugin]], [[vti-didcomm-js]], [[verifiable-trust-agent]], [[coordinated-releases]] diff --git a/content/entities/verifiable-git-infrastructure.md b/content/entities/verifiable-git-infrastructure.md index 92d4fa4..2d18a3b 100644 --- a/content/entities/verifiable-git-infrastructure.md +++ b/content/entities/verifiable-git-infrastructure.md @@ -1,8 +1,8 @@ --- title: "Verifiable Git Infrastructure (VGI) — commit trust for DIDs" type: entity -tags: [vgi, git, signing, ci, trust-registry, know-your-developer, secondary, cypress] -date-updated: 2026-08-19 +tags: [vgi, git, signing, ci, trust-registry, know-your-developer, secondary, cypress, dogwood, supply-chain] +date-updated: 2026-09-18 repo: https://github.com/OpenVTC/verifiable-git-infrastructure --- @@ -20,27 +20,58 @@ It is deliberately *not* a generic git-signing library: you need a VTA to sign a ## Components -Rust workspace (edition 2024, MSRV 1.95), all crates at **0.4.5** (the `Cypress` release, [[coordinated-releases]]): +Rust workspace (edition 2024, MSRV 1.95), three crates versioned in lockstep. The workspace is at **0.4.12** (2026-09-17); it was **0.4.5** at `Cypress` and **0.4.7** at `VTI-Dogwood` ([[coordinated-releases]]). Its dependency line follows the VTA stack: vta-sdk **0.42**, affinidi-tdk 0.16, trql-client 0.18, and a stated floor of `affinidi-did-resolver-cache-sdk` ≥ 0.8.37 (the release whose did:webvh resolver refuses non-public hosts). | Crate | Role | |-------|------| -| **vgi-core** | Dependency-light primitives: SSH-signature (`PROTOCOL.sshsig`) encoder, git commit-object handling, Ed25519 key extraction from DID documents. No network, keyring, or VTA. | -| **did-git-sign** | The signer, installed as git's `gpg.ssh.program`. `did-git-sign init --vta-did …` resolves the VTA, mints a temporary admin did:key, prints the `pnm contexts create …` command for the operator to authorise, then sets `gpg.format=ssh`, `commit.gpgsign=true` and — load-bearing — `user.email = `, the only place a commit names its signer. Refuses to sign when the committer DID and the selected key disagree; writes every attempt to an audit log. Key selection: `DID_GIT_SIGN_KEY` env → `did-git-sign.key` git config → config file (git `includeIf hasconfig:remote.*.url` recommended for people in several communities). | -| **verify-trust** | The CI verifier binary. Depends on vgi-core, a DID resolver ([[affinidi-tdk]]), `trql-client` (TRQP query client) and `vta-sdk` with only the `agent-names` feature for display names. Never opens a VTA session or keyring. | +| **vgi-core** | Dependency-light primitives: SSH-signature (`PROTOCOL.sshsig`) encoder, git commit-object handling, Ed25519 key extraction from DID documents — and, since Dogwood, the `Signed-by-DID:` trailer reader (`signer_did`, `conflicting_signer_dids`), which since v0.4.10 ports git's own trailer-block rules rather than approximating them. No network, keyring, or VTA. | +| **did-git-sign** | The signer, installed as git's `gpg.ssh.program`. `did-git-sign init --vta-did …` resolves the VTA, mints a temporary admin did:key, prints the `pnm contexts create …` command for the operator to authorise, then sets `gpg.format=ssh`, `commit.gpgsign=true`, **`did-git-sign.key = `** (the single identity selector) and `core.hooksPath` to a hook directory it owns. That directory holds a `commit-msg` hook that writes the **`Signed-by-DID:` trailer** via `git interpret-trailers`, plus a delegating stub for every other standard hook so existing repo hooks keep running; `init` refuses to take `core.hooksPath` from husky / lefthook / pre-commit, and `uninstall` removes it all. `user.email` is left alone. Refuses to sign when the claimed DID and the selected key disagree; signs only in the `git` sshsig namespace and only for a `git` / `git-*` / `ssh-keygen` parent; writes every attempt to `audit.log`. Key selection: `DID_GIT_SIGN_KEY` env → `did-git-sign.key` git config → config file (git `includeIf hasconfig:remote.*.url` recommended for people in several communities); the hook reads the same selector in the same order, so key and claim cannot drift. `did-git-sign health --did-jsonl ` checks that a local did:webvh log actually publishes the signing key. | +| **verify-trust** | The CI verifier binary. Depends on vgi-core, a DID resolver ([[affinidi-tdk]], with `HostPolicy::PublicOnly` stated explicitly), `trql-client` (TRQP query client) and `vta-sdk` with only the `agent-names` feature for display names. Never opens a VTA session or keyring. | + +### Where the DID claim lives — the Dogwood change + +Until Cypress the commit's identity claim was `user.email = `: the only place a commit could name its signer, since an sshsig blob carries a raw key and no identity. It worked, but it made every commit unattributable on GitHub and GitLab, which match commits to accounts by email — and it forced people in several communities to keep two settings (`user.email` and the key selector) in step by hand, with a silent wrong-community commit as the failure mode. Dogwood moved the claim into a **`Signed-by-DID:` git trailer** (PR #25, Robert Kwolek, 2026-08-30). The trailer sits inside the commit message, which is part of the signed payload, so it is exactly as tamper-evident as the committer header was; `verify-trust` reads it first and falls back to a DID committer for older commits. A commit carrying *both* a trailer and a DID committer that disagree is a new, fail-closed verdict, `conflictingSignerDids`. ### The GitHub Action -`.github/actions/verify-trust` (composite) downloads a prebuilt `verify-trust` (Linux x64, macOS arm64/x64, Windows x64 — no Rust toolchain on the runner) and runs it over a commit range. For every commit: it must carry a `gpgsig`; the committer must be a DID; that DID must resolve and publish the Ed25519 key that signed; the signature must verify; and the registry must hold a grant for the TRQP tuple `(entity = DID, authority = VTC DID, action = git.commit.sign, resource = owner/repo)`. Inputs of note: `registry-did` (the endpoint is *discovered* from the registry's DID document, preferring TSP → DIDComm → HTTPS), `vtc-did`, `resource` (defaults to the repo — it alone scopes a signer), `fallback-resource` (an org-wide grant), `exempt-keyring` (an armoured PGP keyring so GitHub's own `web-flow` merge/squash key can pass — committed as `.github/trusted-platform-keys.asc`), `max-signers` (bounds the DID resolutions a PR can force), `resolve-agent-names`. Verdicts `trusted` / `exempt` pass; `unsigned`, `noSignerDid`, `unresolvedSigner`, `unknownKey`, `badSignature`, `unauthorized`, `registryUnavailable` fail — **fails closed at every layer, including registry outage**. The docs insist the check be a *required* status check. +`.github/actions/verify-trust` (composite) downloads a prebuilt `verify-trust` (Linux x64, macOS arm64/x64, Windows x64 — no Rust toolchain on the runner; `version:` defaults to `latest`) and runs it over a commit range. For every commit: it must carry a `gpgsig`; it must claim a signer DID (trailer, else DID committer); that DID must resolve — on a *public* host — and publish the Ed25519 key that signed; the signature must verify; and the registry must hold a grant for the TRQP tuple `(entity = DID, authority = VTC DID, action = git.commit.sign, resource = owner/repo)`. Inputs of note: `registry-did` (the endpoint is *discovered* from the registry's DID document, preferring TSP → DIDComm → HTTPS), `vtc-did`, `resource` (defaults to the repo — it alone scopes a signer), `fallback-resource` (an org-wide grant), `exempt-keyring` (an armoured PGP keyring so GitHub's own `web-flow` merge/squash key can pass — committed as `.github/trusted-platform-keys.asc`), `max-signers` (bounds the DID resolutions a PR can force), `resolve-agent-names`. Since v0.4.9 every input reaches the script through `env:` (never `${{ }}` in a `run:` body), `max-signers` must be an integer, and `range` may not look like an option — the CLI puts `--end-of-options` before it. Verdicts `trusted` / `exempt` pass; `unsigned`, `noSignerDid`, `conflictingSignerDids`, `unresolvedSigner` (including a `BlockedHost` reason), `unknownKey`, `badSignature`, `unauthorized`, `registryUnavailable` fail — **fails closed at every layer, including registry outage**. The docs insist the check be a *required* status check. + +### Supply-chain posture of the repo itself + +The September hardening (tracked as **SEC-4045**) treats VGI's own CI as part of the trust boundary, because the Action is published for third-party use and the workflows hold crates.io and Release credentials: every `uses:` is pinned to a commit SHA with Dependabot (weekly, 7-day cooldown) moving pins; tokens are `contents: read` by default; `publish.yml` is split into a `package` job (compiles every crate tarball, no OIDC) and a `publish` job (tag-only, `crates-io` environment, `--no-verify`, so no dependency build script runs beside the registry credential); release tarballs carry build-provenance attestations; a `CODEOWNERS` routes `.github/`, `policy.rs`, `verify-trust/` and the manifests to `@OpenVTC/openvtc-admins`. Two regression gates guard the shape: `action-selftest.yml` runs the Action with shell syntax in its inputs on every PR and fails if any of it executes, and a structural test parses every workflow and asserts no `${{ }}` inside a `run:` scalar and no unpinned `uses:`. ## Relationship to OpenVTC -`did-git-sign` was born inside [[openvtc]] — auto-configured by its setup wizard since v0.1.6, per-repo persona selection under multi-community (T8) — and the verify side landed there first too (openvtc #152 verify-trust, #154 PGP exemption keyring + Action packaging, #155 org-fallback grants + committed web-flow keyring, July 2026). VGI was then extracted with history (first commit 2026-07-18) and openvtc #159 dropped the vendored crate to consume the published one (0.4.5 at Cypress), pinning the Action for its own dogfooding CI (#160). A standing obligation is recorded in both repos: VGI's `vta-sdk` line must track OpenVTC's, or two `vta-sdk` copies land in the OpenVTC binary. +`did-git-sign` was born inside [[openvtc]] — auto-configured by its setup wizard since v0.1.6, per-repo persona selection under multi-community (T8) — and the verify side landed there first too (openvtc #152 verify-trust, #154 PGP exemption keyring + Action packaging, #155 org-fallback grants + committed web-flow keyring, July 2026). VGI was then extracted with history (first commit 2026-07-18) and openvtc #159 dropped the vendored crate to consume the published one (0.4.5 at Cypress), pinning the Action for its own dogfooding CI (#160). A standing obligation is recorded in both repos: VGI's `vta-sdk` line must track OpenVTC's, or two `vta-sdk` copies land in the OpenVTC binary — and the obligation is discharged only by a *published* release, not by main. It has now bitten six times (0.23, 0.25, 0.27, 0.31, 0.32, 0.34…0.39), which is why most post-Dogwood releases are one-line "so OpenVTC can move" bumps. ## Recent Development +### Post-Dogwood — v0.4.8 → v0.4.12 — 2026-09-08 → 09-17 (#37–#57) + +The month after Dogwood had two threads: finishing the release that Dogwood had half-shipped, and a security pass (SEC-4045) that turned findings against the CI and the signer into permanent gates. + +- **Un-sticking crates.io** (#37–#40, v0.4.8, 09-10): the v0.4.7 publish had gone up *partially* — `vgi-core` 0.4.7 landed, then `verify-trust` was rejected because it took `trql-client` by git `rev`. The registry publishing trql-client 0.16 (then 0.17, matching vta-sdk 0.35's trust-tasks line) let the pin go back to a plain version, and the 0.4.8 bump exists so vgi-core republishes with its trailer code rather than being skipped by the resumable loop. `cargo tree -i trust-tasks-rs` is one node again. +- **SEC-4045 hardening** (v0.4.9, 09-11/12): the composite Action pasted `${{ inputs.* }}` into its shell (#42 — an input holding `$(…)` ran as code; now `env:` + argument array, plus the git `--end-of-options` guard); the `DID_GIT_SIGN_BYPASS_POLICY=1` escape hatch was compiled out of released binaries and the parent-process gate is now documented honestly as an *accident guard, not a boundary* — the key is protected by the VTA credential in the OS keyring, not by the gate (#44); actions pinned by SHA, tokens narrowed, publish split so no dependency build script runs beside the crates.io credential (#45). +- **Fail-closed, but also honest** (#47, #48, v0.4.10 09-13): a property test against git's own two trailer views found seven places where vgi-core's trailer reader disagreed with git — never exploitable (every decision already fails closed) but a *display* differential, where the DID a reviewer reads in `git log` was not the DID verified. `commit.rs` now ports git's `find_trailer_block_start` / `find_separator` / unfolding rules; the last `Signed-by-DID` wins and only if it is a DID. And because every DID the verifier resolves is chosen by the PR author, did:webvh resolution is now guarded by the resolver's public-host policy (didwebvh-rs 0.7 via cache-sdk 0.8.37) — closing an SSRF primitive on self-hosted runners; an internal-host DID fails `unresolvedSigner` / `BlockedHost` without a connection being made (#48). Structural regression gates for the YAML shape (#52); `CODEOWNERS`; Dependabot holds `rand` at 0.8 until `pgp` supports 0.9 (#49); a hook-test `ETXTBSY` race serialised (#54). +- **Follow the SDK** (v0.4.11 09-16, v0.4.12 09-17): vta-sdk 0.38 (drops the duplicate didwebvh-rs 0.6.1, #51) → 0.39 (#55/#56) → **0.42** with TDK 0.16 and trql-client 0.18 (#57), each a no-source-change bump so OpenVTC's graph holds one sdk. None of the intervening vta-sdk breaking changes (generic `verify_trust_task_proof_with`, ML-DSA `KeyType` variants, `#[non_exhaustive]` wire bodies) touches this workspace — it reaches the VTA only through `connect_auto` and display names. +- The README, crate READMEs and RUNBOOK still pin the Action example at `@v0.4.6`; the self-test pins `v0.4.8`; the Action's own default is `latest`. + +### VTI-Dogwood — v0.4.6 / v0.4.7 — 2026-08-22 → 08-30 (#25, #32–#35) + +Dogwood was a "silent" release across the stack, and in VGI it carried the one user-visible change of the period: **the identity claim moved from `user.email` to a `Signed-by-DID:` trailer**, so commits are attributable on forges and there is nothing left to keep in step by hand (see above). PR #25 (Robert Kwolek, Affinidi — "git trailer for git-host email compatibility") merged 08-30 with a review pass from Glenn Gore that made the hook portable (GNU sed had hung `git commit` on Linux; a one-line message got the trailer glued to its subject where git's parser saw nothing), made `Signed-off-by:` opt-in (`did-git-sign.signoff` — "a signing tool must not assert DCO on the user's behalf"), had `init` refuse to steal `core.hooksPath` in *both* scopes, and replaced tests that grepped the hook's source with tests that run it. `did-git-sign health --did-jsonl` arrived in the same PR. + +- `VTI-Dogwood-RC-1` = **v0.4.6** (08-22): vta-sdk 0.25 → 0.27, "the line the VTA now deploys" — the third time the standing obligation was found from OpenVTC's `cargo tree -d`, so the manifest note was rewritten to say it is discharged only by a published release. It left a tolerated trust-tasks 0.9 / 0.11 duplicate confined to verify-trust. +- `VTI-Dogwood` and `VTI-Dogwood-R1` both point at the same commit (#35, 08-30): **v0.4.7** (vta-sdk 0.32, TDK 0.10, trql-client on a git `rev` for the trust-tasks 0.17 line, #33) plus a **resumable `publish.yml`** (#35) that skips crates already on crates.io — written *because* the v0.4.7 tag had half-published. At Dogwood crates.io therefore held vgi-core 0.4.7 beside verify-trust / did-git-sign 0.4.6; that was only reconciled by v0.4.8. +- **Client identity on VTA auth** (#36, 09-01/02, Robert Kwolek): the cached-token path now builds `VtaClient` with a `ClientIdentity::did_key(...)`, matching the identity-bearing authentication the VTA moved to. Notably, that commit is itself carrying a `Signed-by-DID:` trailer — the first dogfooded one in the history. + +### Cypress and before — 2026-07-18 → 08-17 + - **v0.1.1 — 2026-07-18** — workspace import, release pipeline, download-based Action, crates.io trusted publishing, per-crate READMEs. **v0.1.2 — 07-25** — signers reported by agent name. - **v0.4.0 — 2026-07-26** — three breaking PRs (#12–#14): derive the signer set from the commits themselves and drop the `.did-signers` file; discover the registry endpoint from its DID and name the VTC; write `user.email` as the signing DID with a sign-time guard. **v0.4.1** (same day) — security hardening: atomic `allowed_signers` writes, host parsing before any cleartext-to-loopback decision, a triage record under `docs/security/`. - **v0.4.2 → v0.4.5 — 2026-08-10 → 08-17** — dependency lockstep with the VTA stack: trust-tasks-rs 0.4, vta-sdk 0.23 / 0.24 / 0.25, trql-client 0.13 → 0.14 (collapsing three trust-tasks copies). v0.4.5 is the `Cypress` snapshot; the repo also carries the shared `VTI-Cypress-RC-*` tags. -- **Direction**: stay in lockstep with vta-sdk; verify-trust currently only *constructs* HTTPS transport to the registry even though discovery prefers TSP/DIDComm. -See also: [[openvtc]], [[verifiable-trust-agent]], [[trust-registries]], [[verifiable-trust-community]], [[first-person-network]] +### Direction + +Stay in lockstep with vta-sdk (each SDK minor now produces a VGI patch release within a day or two); verify-trust still only *compiles* HTTPS transport to the registry even though discovery prefers TSP/DIDComm — the code refuses explicitly if discovery selects a transport it cannot use; `VTI-Eucalyptus-RC-0` (2026-09-17) is in flight across the stack but not yet tagged here. + +See also: [[openvtc]], [[verifiable-trust-agent]], [[trust-registries]], [[verifiable-trust-community]], [[first-person-network]], [[coordinated-releases]] diff --git a/content/entities/verifiable-trust-agent.md b/content/entities/verifiable-trust-agent.md index 2965b88..5ffbb52 100644 --- a/content/entities/verifiable-trust-agent.md +++ b/content/entities/verifiable-trust-agent.md @@ -1,8 +1,8 @@ --- title: "Verifiable Trust Agent (VTA)" type: entity -tags: [vta, vti, key-management, signing-oracle, infrastructure, primary, mobile, tsp, mdoc] -date-updated: 2026-08-19 +tags: [vta, vti, key-management, signing-oracle, infrastructure, primary, mobile, tsp, mdoc, post-quantum, audit, dogwood, eucalyptus] +date-updated: 2026-09-18 --- # Verifiable Trust Agent (VTA) @@ -16,25 +16,26 @@ The Verifiable Trust Agent is the central service of the [[verifiable-trust-infr The VTA is a **signing oracle** — applications send it data to sign, and it returns signatures. The private keys never leave the VTA's security boundary. This means applications that need to issue [[verifiable-credentials|credentials]], update [[decentralized-identifiers|DIDs]], or send authenticated [[didcomm|DIDComm messages]] don't need to manage keys themselves. Key capabilities: -- **Key generation and derivation** — keys derive from a single BIP-39 seed via [[bip32-key-derivation|BIP-32]] (Ed25519, X25519, P-256); since August 2026 the VTA can also hold **non-extractable internal keys** (no derivation path, never exported, excluded from backup) and **imported** Ed25519 keys for deterministic did:keys -- **Signing oracle** — sign payloads on behalf of applications without exposing keys, behind a three-gate authorization model (caller context scope → resource-bound `signable_keys` policy → unscoped keys super-admin-only), narrowable per actor with `allowedKeys` -- **DID management** — create and manage [[did-webvh|did:webvh]], did:key and did:peer identifiers, including human-readable **agent names** (`example.com/@alice`) claimed in `alsoKnownAs` -- **Credential holder (vault)** — receive, store, verify and present credentials in W3C Data Integrity, BBS, SD-JWT and (August 2026) **ISO mdoc** formats, over DIDComm/TSP credential-exchange and OID4VP -- **Session management** — DID-keyed sessions; JWT-based REST auth with DIDComm/TSP challenge-response -- **Access control** — role-based ACL (Super Admin → Admin → Initiator → Application → Reader → Monitor) with context scope and least-privilege approvers ("may approve" ≠ "may act") -- **Approvals** — one runtime-manageable approvals model (`pnm approvals`) driven by Rego policy rules: step-up, task-execution consent pushed to approver devices, and an offline break-glass -- **Backup and restore** — encrypted backups using Argon2id (15-character minimum password) -- **Audit logging** — every operation is logged for compliance +- **Key generation and derivation** — keys derive from a single BIP-39 seed via [[bip32-key-derivation|BIP-32]] (Ed25519, X25519, P-256, and since September 2026 **ML-DSA-44 / ML-DSA-65 post-quantum keys**, each record carrying the algorithm it was minted with); the VTA can also hold **non-extractable internal keys** (no derivation path, never exported, excluded from backup), **imported** Ed25519 keys for deterministic did:keys, and any key can be marked `exportable: false` so it can only ever be *used* +- **Signing oracle** — sign payloads on behalf of applications without exposing keys, behind a three-gate authorization model (caller context scope → resource-bound `signable_keys` policy → unscoped keys super-admin-only), narrowable per actor with `allowedKeys`; opaque payloads are **domain-separated** so a signature obtained for one purpose cannot verify as something else, and every use leaves an audit row +- **DID management** — create and manage [[did-webvh|did:webvh]], did:key and did:peer identifiers from templates (did-templates 2.0 and 3.0, the latter declaring which algorithms each key slot uses), including human-readable **agent names** (`example.com/@alice`) claimed in `alsoKnownAs`; deleting a DID cascades, refuses or revokes across everything that referenced it +- **Four holder stores** — the **secrets vault**; the **credential vault** (receive, store, verify and present W3C Data Integrity, BBS, SD-JWT and ISO mdoc credentials over DIDComm/TSP credential-exchange and OID4VP); versioned, namespaced **application state** (`vta/app-state/*`, August 2026); and the **persona store** (`persona/*`, September 2026 — the holder's own attributes, the profiles projected over them, disclosure history, behind a one-way boundary a context cannot read across). Plus per-context **agent memory** with separate read/write grants +- **Session management** — DID-keyed sessions; JWT-based REST auth with DIDComm/TSP challenge-response; every authenticated client carries an identity, signs what it sends, and verifies the signed reply +- **Access control** — role-based ACL (Super Admin → Admin → Initiator → Application → Reader → Monitor) with context scope, per-entry **capabilities that narrow within a role** (enforced since September 2026), a `persona-holder` capability, and least-privilege approvers ("may approve" ≠ "may act") +- **Approvals** — one runtime-manageable approvals model (`pnm approvals`) driven by Rego policy rules: step-up, task-execution consent pushed to approver devices (signed, and verified on-device), and an offline break-glass +- **Backup and restore** — encrypted backups using Argon2id (15-character minimum password), written owner-only; a DIDComm/TSP-only VTA backs up over chunked Trust Tasks +- **Audit logging** — a **hash-chained, verifiable** log with its own audit key; actors and DID-shaped targets are kept under a keyed hash so an erasure can null the plaintext while the chain still verifies; failed audit writes are reported, never swallowed +- **Rate limiting** — three per-IP limiters (`auth`, `did-log`, `backup-blob`), tunable at runtime, with attributable 429s ## Architecture The VTA is built with Axum (Rust async web framework). Every operation is a **Trust Task** — a versioned JSON document with a canonical `trusttasks.org/spec/*` URI — and the same document can arrive over three transports: -1. **[[trust-spanning-protocol|TSP]]** — preferred since mid-2026; a VTA can now run TSP-only +1. **[[trust-spanning-protocol|TSP]]** — preferred since mid-2026, now at **Rev 3** (September 2026): a VTA forms and persists §7.2.2 relationships with its peers, re-forms them after a silent drop, and routes cross-mediator sends *nested* so its own mediator never learns the recipient; a VTA can run TSP-only 2. **[[didcomm|DIDComm v2]]** — encrypted messages via a mediator (the interop fallback) -3. **REST** — HTTP endpoints authenticated with EdDSA JWTs; the Trust-Task document now rides the HTTPS binding too, and every superseded REST route is sign-posted with its successor task and a usage metric that gates its eventual removal +3. **REST** — HTTP endpoints authenticated with EdDSA JWTs; the Trust-Task document rides the HTTPS binding too, and every superseded REST route is sign-posted with its successor task and a usage metric that gates its eventual removal -All paths converge on one operations layer. Storage uses fjall, an embedded LSM key-value store, with AES-GCM at-rest encryption (TEE-derived keys in enclave mode; the `[hardened]` mode for non-TEE deployments since August 2026). Since July 2026 the service is a thin "spine" over eleven subsystem crates (`vta-keys`, `vta-vault`, `vta-policy`, `vta-webvh`, `vta-tee`, `vta-backup`, …) — see [[verifiable-trust-infrastructure#Components]]. +All inbound paths converge on one **dispatch spine** that enforces what each Trust-Task spec declares for itself — `recipient`, `proof`, audience binding, `issuedAt`, a replay guard whose record lives exactly as long as the acceptance window, and idempotency-key deduplication so a retried request whose reply was lost cannot mint a second DID. All *outbound* Trust Tasks go through one transport seam that picks TSP > DIDComm > REST from what the peer advertises and the sender can actually do. Every response is signed. Storage uses fjall, an embedded LSM key-value store, with AES-GCM at-rest encryption (TEE-derived keys in enclave mode; the `[hardened]` mode for non-TEE deployments since August 2026). Since July 2026 the service is a thin "spine" over twelve subsystem crates (`vta-keys`, `vta-vault`, `vta-policy`, `vta-webvh`, `vta-tee`, `vta-backup`, `vta-persona`, …) — see [[verifiable-trust-infrastructure#Components]]. The whole surface is declared an implementation of the normative ToIP [VTI specification](https://trustoverip.github.io/dtgwg-vti-spec/). ### Application Contexts — now hierarchical @@ -60,21 +61,22 @@ The VTA can run inside an AWS Nitro Enclave — a hardware-isolated virtual mach - Keys are unsealed via AWS KMS, pinned to the enclave's attestation (PCR0 + PCR8) - Communication happens over vsock (virtual socket) rather than network - Since August 2026 tenant config is **not baked into the enclave image**: one image / one PCR0 per fleet, the config envelope is delivered over vsock at boot and its digest is anchored in the attestation (`POST /attestation/config-report`) -- An 8-layer defense-in-depth security model protects key material; TEE anti-rollback via an external CAS counter -- An enclave proxy handles external routing +- An 8-layer defense-in-depth security model protects key material; TEE anti-rollback via an external CAS counter; a KMS re-initialisation needs explicit `allow_kms_reinit` authorization whatever the failure class (September 2026) +- An enclave proxy handles external routing (it now resolves `nitro-cli` by absolute path with a scrubbed environment) +- An operator connecting `pnm` to a TEE VTA anchors the bootstrap **by DID and a pinned PCR0**, never by a guessed URL -A plain non-TEE container image (with `[hardened]` at-rest encryption) also exists since August 2026, and the personal-use path is a managed VTA on the **VTA Farm** — see [[vti-setup]]. +A plain non-TEE container image (with `[hardened]` at-rest encryption) also exists since August 2026, and the personal-use path is a managed VTA on the **VTA Farm** — see [[vti-setup]], [[vtafarm]]. ### Seed Storage Backends The master seed can be stored in: - OS keyring (default for development) -- AWS Secrets Manager / GCP Secret Manager / Azure Key Vault +- AWS Secrets Manager / GCP Secret Manager / Azure Key Vault — seed reads are now **cached**, so KMS decrypt traffic scales with time rather than with request volume (a production VTA at ~22 req/s had been billing ~58M KMS requests a month) - KMS (for enclave mode) - Config file (not recommended for production) ## SDK Integration -Third-party services integrate with the VTA via the `vta-sdk` crate (0.25 at the `Cypress` release — consumed by [[openvtc]], [[affinidi-webvh-service|did-hosting-service]], the [[affinidi-tdk|TDK]] mediator, [[verifiable-git-infrastructure|VGI]], and the [[vta-browser-plugin]]'s generated type bindings): +Third-party services integrate with the VTA via the `vta-sdk` crate (0.25 at `Cypress`, 0.32 at `VTI-Dogwood`, 0.42 at `VTI-Eucalyptus-RC-0`, 0.43 at HEAD — consumed by [[openvtc]], [[affinidi-webvh-service|did-hosting-service]], the [[affinidi-tdk|TDK]] mediator, [[verifiable-git-infrastructure|VGI]], [[vtafarm]], and the [[vta-browser-plugin]]'s generated type bindings): ```rust // Simplified integration pattern @@ -84,12 +86,25 @@ let vta = integration::startup(&config).await?; let signature = vta.sign(payload).await?; ``` -The SDK handles authentication, token refresh, secret caching, and offline fallback. This is the recommended way for services in the ecosystem (like the [[affinidi-webvh-service]]) to interact with the VTA. +The SDK handles authentication, token refresh, secret caching, and offline fallback. Since Dogwood a client is constructed *with* its identity (`VtaClient::authenticated(url, identity, token)`), signs every Trust Task it sends, verifies the signed reply, holds one idempotency key across the attempts of an operation (`VtaClient::idempotent`), vets a DID-advertised endpoint before sending a token to it, and re-forms a dropped TSP relationship on a reply timeout. Wire types are generated from the published Trust-Task specs and are `#[non_exhaustive]`, so adding a field is no longer a semver break. This is the recommended way for services in the ecosystem (like the [[affinidi-webvh-service]]) to interact with the VTA. ## Recent Development Per-release detail lives on the workspace entity — see [[verifiable-trust-infrastructure#Recent Development]] for the full activity log. VTA-relevant highlights, reverse chronological: +### Dogwood and the Eucalyptus RC — August–September 2026 + +The VTA went through two milestone tags in a month ([[coordinated-releases]]). **`VTI-Dogwood`** (2026-08-30; `vta-service` 0.23.3 / `vta-sdk` 0.32.2; re-cut as **`VTI-Dogwood-R1`** on 09-01 at 0.23.4 / 0.32.3 with five "say the right thing" fixes) was deliberately silent — no GitHub Release — because its content is invisible to a user and essential to anyone building on the VTA: the wire became *honest*. **`VTI-Eucalyptus-RC-0`** (2026-09-17; 0.33.0 / 0.42.1; HEAD 0.34.0 / 0.43.0 the next day) is the loud one — post-quantum keys, TSP Rev 3, a hash-chained audit log, and the VTA becoming the holder's whole agent. + +- **Dogwood: the wire is honest.** The dispatch spine enforces what each spec declares — `recipient`, `proof`, audience, `issuedAt` (#1146), framework-0.5.0 freshness and replay bounds (#1117, #1126/#1127) — and error messages stop being a probing oracle (#1130). Retries are safe: keyed Trust Tasks dedup on an `idempotencyKey` held across attempts (#1011/#1012; `retry-and-idempotency.md`). Every client has an identity and signs (#1147), and **any DID that names a key may sign** — the `did:key`-only restriction that had locked provisioned did:webvh integrations out of all 210 proof-requiring tasks is gone (#1193). Task coverage measured at 102 of 109 specs (#1151); three response-shape defects found by validating real responses (#1114). A third store, **application state** (#1051); `vta/services` as a task family superseding twenty REST routes (#1017); canonical capability discovery (#1042); DID deletion cascade with dry-run (#1198/#1199). +- **Signed responses, verified replies.** Both services sign success responses (#1335) and `VtaClient` verifies every reply (#1341); the phone verifies the consent prompt, not just the step-up one (#1324). +- **Post-quantum.** `KeyType::{MlDsa44, MlDsa65}` (#1502) derived from the BIP-32 chain (#1505); key records carry their algorithm (#1532); templates declare per-slot algorithms and a third key slot (#1530, #1554); did-templates 3.0 accepted alongside 2.0 (#1538); `pnm keys create` for a PQ key (#1535). Hybrid (multi-proof) credential issuance and verification landed on the VTC side (#1548–#1557). +- **TSP Rev 3.** Flag-day adoption (#1512); Trust Tasks in the TSP binding envelope (#1478); relationships persisted (#1531), formed before sends and pings, answered (#1525) and re-formed after silent drops on client and server (#1544, #1549); **one outbound path** for every Trust Task chosen from the peer's advertisement (#1474, #1483); cross-mediator sends nested for metadata privacy (#1559); the transport seam selects by the sender's *live* capability, fixing the post-Dogwood VTC-setup regression (#1560). +- **Security sprint (09-10 → 09-12).** Hash-chained audit log with its own non-derived key and a verifier (#1419–#1421), failed audit writes reported and the signing oracle audited (#1443); **domain-separated opaque signing** (#1417); `exportable` keys and `keys/export-secret` replacing `seeds/export-mnemonic` (#1401, #1404, #1407); DID-advertised endpoints vetted and tokens bound to origin (#1436); did:webvh resolution refused to non-public hosts (#1448); `/auth/challenge` no longer discloses enrolment (#1405); backups written 0600 (#1438); CI actions SHA-pinned (#1440). +- **Authorization surface.** ACL capabilities enforced and settable (#1279/#1280); `persona-holder` (#1286) and `whoami` reporting capabilities (#1298); `MemoryRead`/`MemoryWrite` (#1234) with `pnm memory` (#1222); runtime-tunable, attributable rate limits with `did.jsonl` on its own bucket (#1510, #1519); cached seed reads (#1290). +- **The holder's whole agent.** `vta-persona` — attributes, profiles, bindings, disclosure history, `release: stepUp` on disclosure, a deployment-declared claim-type registry (#1255 → #1342); the VTA as a **data-room** member — it mints the credentials that make a room joinable, seals records, joins, keeps up, opens what it holds, and calls a room's host on its principal's behalf (#1326, #1329, #1332, #1250); `vta-mcp` gains a local operation guard, per-call logging and a proper doc (#1101, `docs/02-vta/vta-mcp.md`). +- **TEE / ops.** `allow_kms_reinit` fail-closed (#1249); pnm anchors TEE bootstrap by DID + pinned PCR0 (#1454); `nitro-cli` by absolute path (#1441); chunked Trust-Task backup for DIDComm/TSP-only VTAs (#1522); `vta-mobile-core` on uniffi 0.32 and Rev 3 sealing. + ### Cypress + convergence — July–August 2026 The VTA shipped in the coordinated **`Cypress`** release ([[coordinated-releases]], 2026-08-17) as `vta-service` 0.17.0 / `vta-sdk` 0.25.0 — the first release cut through formal RCs and the new release-plz process. The month's VTA-relevant themes: @@ -188,6 +203,6 @@ A single Trust Task envelope can be accepted at one of four ladder rungs: **devi - P-256 keys - Prometheus metrics -The direction has shifted twice: first from "make the VTA usable" to "make the VTA's runtime surface mutable in production without downtime or rebuilds," and now (mid-2026) to broadening *who* the VTA serves — phones as cryptographic approvers, AI agents as first-class clients, enterprises with owner/user separation of duty — over a transport stack converging on TSP. +The direction has shifted three times: from "make the VTA usable," to "make the VTA's runtime surface mutable in production without downtime or rebuilds," to (mid-2026) broadening *who* the VTA serves — phones as cryptographic approvers, AI agents as first-class clients, enterprises with owner/user separation of duty — and now (September 2026) to broadening *what it holds for its human*: identity attributes, memory, application state and shared data rooms, behind a signed, spec-conformant, post-quantum-ready wire over TSP Rev 3. See also: [[verifiable-trust-infrastructure]], [[bip32-key-derivation]], [[openvtc]] diff --git a/content/entities/verifiable-trust-infrastructure.md b/content/entities/verifiable-trust-infrastructure.md index 067bd84..2ab0b28 100644 --- a/content/entities/verifiable-trust-infrastructure.md +++ b/content/entities/verifiable-trust-infrastructure.md @@ -1,8 +1,8 @@ --- title: "Verifiable Trust Infrastructure (VTI)" type: entity -tags: [vti, infrastructure, workspace, primary, cypress, tsp, trust-tasks] -date-updated: 2026-08-19 +tags: [vti, infrastructure, workspace, primary, cypress, dogwood, eucalyptus, tsp, trust-tasks, post-quantum, data-rooms] +date-updated: 2026-09-18 repo: https://github.com/OpenVTC/verifiable-trust-infrastructure --- @@ -14,40 +14,45 @@ The Verifiable Trust Infrastructure is a Rust workspace containing the core serv ## Components -The workspace has grown to **26 members**: in late July 2026 the monolithic `vta-service` was decomposed into eleven subsystem crates (`docs/05-design-notes/vta-service-decomposition.md` — vta-service/src shrank from ~114k to ~87k lines, −23%), on top of the June additions (secrets, AI-agent, VTC-client, fuzzing). Versions shown are those in the coordinated **`Cypress`** release ([[coordinated-releases]], 2026-08-17); ★ = new since July 2026. - -| Crate | Purpose | Cypress | -|-------|---------|---------| -| **[[verifiable-trust-agent\|vta-service]]** | The VTA server "spine" — HTTP routes, Trust-Task dispatch, messaging bridge, orchestration; re-exports the subsystem crates | 0.17.0 | -| **vta-sdk** | Public client SDK / data model — consumed by eight sibling repos (OpenVTC, did-hosting-service, the TDK mediator, VGI, the browser plugin's type bindings …) | 0.25.0 | -| ★ **vta-config** | `AppConfig` TOML shape and sub-configs | 0.3.8 | -| ★ **vta-keyspaces** | Keyspace-name registry (shared storage vocabulary) | 0.1.4 | -| ★ **vta-audit** | `audit!` tracing macro + audit-keyspace persistence | 0.1.6 | -| ★ **vta-keys** | Master-seed storage, BIP-32 derivation, key wrapping, seed-store backends — and, since 0.2.5, non-extractable internal keys | 0.2.5 | -| ★ **vta-vault** | Holder credential vault: store / query / receive / verify / present / status-refresh — Data Integrity, BBS, SD-JWT and (since 0.3.0) ISO mdoc | 0.3.0 | -| ★ **vta-webvh** | WebVH hosting infrastructure: DID-record store, client to a did:webvh host, DID-auth handshake | 0.1.9 | -| ★ **vta-policy** | Regorus (Rego) Policy Decision Point, default policy bundle, consent model, decision evaluators | 0.2.7 | -| ★ **vta-support** | Mid-layer services: trust-context storage, sealed-transfer helper, sealed-bootstrap nonce store | 0.2.7 | -| ★ **vta-tee** | Nitro / SEV-SNP attestation providers, KMS attest/decrypt + storage-key derivation, anchor MAC, first-boot DID autogen | 0.1.8 | -| ★ **vta-backup** | Encrypted full-state export/import, sealed backup-bundle store + TTL sweeper | 0.1.10 | -| ★ **vta-sweepers** | Background TTL sweepers (ACL grant expiry, pending consent, soft-deleted vault purge) | 0.1.3 | -| **vta-enclave** | AWS Nitro Enclave binary (TEE mode) — *not published* | 0.7.7 | -| **vta-mobile-core** | UniFFI engine behind the Authenticator + PNM mobile apps (DIDComm/TSP, Trust Tasks, AAL step-up); Android AAR / iOS xcframework | 0.6.18 | -| **vta-mcp** | MCP stdio server bridging VTA capabilities to MCP hosts like Claude Desktop — *not published* | 0.1.5 | -| **vti-webauthn** | DID-VM-resolved WebAuthn verifier | 0.1.1 | -| **vtc-service** | [[verifiable-trust-community\|VTC]] daemon — community lifecycle, policies, credentials, public website, admin UI — *not published* (`publish = false`) | 0.11.58 | -| **vtc-client** | Thin client SDK for a VTC — the VTC counterpart to vta-sdk | 0.3.7 | -| **vti-common** | Shared auth, ACL, storage, config, error handling; `context_path` (hierarchical contexts) | 0.12.1 | -| **vti-secrets** | Pluggable secret-store backends (plaintext, HashiCorp Vault, KMS/TEE, Kubernetes Secrets) | 0.1.14 | -| **vta-cli-common** | Shared CLI command implementations | 0.11.0 | -| **cnm-cli** | Community Network Manager (multi-community client) | 0.11.22 | -| **pnm-cli** | Personal Network Manager (single-VTA client) | 0.12.6 | -| **didcomm-test** | DIDComm connectivity test harness — *not published* | 0.6.9 | -| **tests/e2e** | In-process end-to-end harness (MockVta / MockVtc) — *not published* | 0.6.0 | - -(`vti-fuzz` is a nested cargo-fuzz workspace, not a member.) Layering: L0 vti-common / vta-sdk / vti-secrets → L1 keyspaces / config / audit → L2 support / keys / vault / webvh / policy → L3 tee / backup / sweepers → L4 vta-service. - -The `vti-didcomm-js` crate (JavaScript DIDComm primitives + spec test vectors) was extracted into its own repository in May 2026 — see [[vti-didcomm-js]]. **Publishing** moved in August 2026 to **release-plz** (#938): merging a PR is no longer releasing — release-plz keeps one `chore: release` PR open, and merging *that* tags each crate (`-v`), generates per-crate CHANGELOGs from conventional commits, runs `cargo-semver-checks`, and publishes via crates.io trusted publishing (GitHub OIDC). 20 of 26 crates publish; the six internal ones are vtc-service, vta-enclave, vta-mcp, vta-mobile-core, didcomm-test, vti-fuzz. +The workspace has grown to **31 members**: in late July 2026 the monolithic `vta-service` was decomposed into eleven subsystem crates (`docs/05-design-notes/vta-service-decomposition.md`), and September 2026 added a **twelfth holder store (`vta-persona`)** plus the four **data-rooms** crates. Versions are shown at the coordinated **`Cypress`** release ([[coordinated-releases]], 2026-08-17), at **`VTI-Dogwood`** (2026-08-30), at **`VTI-Eucalyptus-RC-0`** (2026-09-17) and at HEAD (the `chore: release` merge of 2026-09-18, #1552). ★ = new since Cypress. Because release-plz only bumps *published* crates, the `publish = false` members (vtc-service, vta-enclave, vta-mobile-core, vta-mcp, didcomm-test) keep a frozen manifest version even though their code moved — read those numbers as labels, not as release markers. + +| Crate | Purpose | Cypress | Dogwood | Euc-RC-0 | HEAD | +|-------|---------|---------|---------|----------|------| +| **[[verifiable-trust-agent\|vta-service]]** | The VTA server "spine" — HTTP routes, Trust-Task dispatch spine (replay/freshness/idempotency/proof gates), outbound transport seam, orchestration; re-exports the subsystem crates | 0.17.0 | 0.23.3 | 0.33.0 | 0.34.0 | +| **vta-sdk** | Public client SDK / data model — consumed by eight sibling repos (OpenVTC, did-hosting-service, the TDK mediator, VGI, the browser plugin's type bindings …); since Dogwood every client carries an identity, signs what it sends and verifies what it receives | 0.25.0 | 0.32.2 | 0.42.1 | 0.43.0 | +| **vta-config** | `AppConfig` TOML shape and sub-configs (now including runtime-tunable `[server]` rate-limit quotas) | 0.3.8 | 0.4.1 | 0.5.2 | 0.5.3 | +| **vta-keyspaces** | Keyspace-name registry (shared storage vocabulary) | 0.1.4 | 0.2.4 | 0.2.9 | 0.2.9 | +| **vta-audit** | Audit sink: `audit!` macro, best-effort recording, and (since 0.3.x) a **hash-chained, keyed-actor** log with a `verify` pass | 0.1.6 | 0.3.1 | 0.3.12 | 0.3.13 | +| **vta-keys** | Master-seed storage, BIP-32 derivation, key wrapping, seed-store backends; non-extractable internal keys; since 0.6 **ML-DSA-44/65 post-quantum keys**, per-key `exportable`, and a domain-separated opaque signing oracle | 0.2.5 | 0.4.1 | 0.6.0 | 0.6.1 | +| **vta-vault** | Holder credential vault: store / query / receive / verify / present / status-refresh — Data Integrity, BBS, SD-JWT, ISO mdoc | 0.3.0 | 0.5.1 | 0.5.12 | 0.5.13 | +| **vta-webvh** | WebVH hosting infrastructure: DID-record store, client to a did:webvh host, DID-auth handshake, DID-deletion cascade | 0.1.9 | 0.2.2 | 0.2.13 | 0.2.14 | +| **vta-policy** | Regorus (Rego) Policy Decision Point, default policy bundle, consent model, decision evaluators | 0.2.7 | 0.3.1 | 0.3.12 | 0.3.13 | +| **vta-support** | Mid-layer services: trust-context storage, sealed-transfer helper, sealed-bootstrap nonce store | 0.2.7 | 0.3.1 | 0.3.12 | 0.3.13 | +| **vta-tee** | Nitro / SEV-SNP attestation providers, KMS attest/decrypt + storage-key derivation, anchor MAC, first-boot DID autogen | 0.1.8 | 0.2.1 | 0.2.9 | 0.2.9 | +| **vta-backup** | Encrypted full-state export/import, sealed backup-bundle store + TTL sweeper; since 0.4 a chunked Trust-Task backup for DIDComm/TSP-only VTAs | 0.1.10 | 0.3.1 | 0.4.1 | 0.4.2 | +| **vta-sweepers** | Background TTL sweepers (ACL grant expiry, pending consent, soft-deleted vault purge) | 0.1.3 | 0.3.2 | 0.3.7 | 0.3.7 | +| ★ **vta-persona** | The holder's own identity: attribute pool, profiles projected over it, bindings to persona DIDs, contacts peers disclosed — implements `persona/*`; the fourth holder store (beside secrets vault, credential vault, app-state) | — | — | 0.3.9 | 0.3.9 | +| **vta-enclave** | AWS Nitro Enclave binary (TEE mode) — *not published* | 0.7.7 | 0.7.7 | 0.7.7 | 0.7.7 | +| **vta-mobile-core** | UniFFI engine behind the Authenticator + PNM mobile apps (DIDComm/TSP, Trust Tasks, AAL step-up); Android AAR / iOS xcframework — *not published* (version frozen; code moved to uniffi 0.32, verified consent prompts, Rev 3 TSP) | 0.6.18 | 0.6.18 | 0.6.18 | 0.6.18 | +| **vta-mcp** | MCP stdio server bridging VTA capabilities to MCP hosts (Claude Code / Claude Desktop) — *not published*; now with a local operation guard, per-call logging and `docs/02-vta/vta-mcp.md` | 0.1.5 | 0.1.5 | 0.1.5 | 0.1.5 | +| **vti-webauthn** | DID-VM-resolved WebAuthn verifier | 0.1.1 | 0.2.0 | 0.2.2 | 0.2.2 | +| **vtc-service** | [[verifiable-trust-community\|VTC]] daemon — community lifecycle, policies, credentials, personhood, vetting, trust graph, public website, admin UI — *not published* (`publish = false`, version frozen) | 0.11.58 | 0.11.58 | 0.11.58 | 0.11.58 | +| **vtc-client** | Thin client SDK for a VTC — the VTC counterpart to vta-sdk; any DID method can hold (0.6) | 0.3.7 | 0.5.1 | 0.6.8 | 0.6.9 | +| **vti-common** | Shared auth, ACL, storage, config, error handling; `context_path`; capability enforcement; the audit-chain writer both services use | 0.12.1 | 0.16.0 | 0.19.1 | 0.19.2 | +| **vti-secrets** | Pluggable secret-store backends (plaintext, HashiCorp Vault, KMS/TEE, Kubernetes Secrets); cached seed reads since 0.3.x | 0.1.14 | 0.3.1 | 0.3.12 | 0.3.13 | +| **vta-cli-common** | Shared CLI command implementations | 0.11.0 | 0.12.2 | 0.17.1 | 0.17.2 | +| **cnm-cli** | Community Network Manager (multi-community client); vetting admin | 0.11.22 | 0.13.2 | 0.16.3 | 0.16.4 | +| **pnm-cli** | Personal Network Manager (single-VTA client); `pnm memory`, `pnm persona`, `pnm rooms`, `pnm keys create --type ml-dsa-*` | 0.12.6 | 0.14.2 | 0.17.2 | 0.17.3 | +| ★ **vti-rooms** | Data-room storage, wire types and authorization — the parts of a room that are not a service (MLS group custody via openmls, record sealing, epoch key chain) | — | — | 0.2.9 | 0.2.9 | +| ★ **vti-rooms-dtg** | The DTG-credential chain verifier for data rooms | — | — | 0.2.7 | 0.2.8 | +| ★ **vti-rooms-wasm** | A room's member half compiled to WebAssembly so a browser can be a room member — *not published* | — | — | 0.1.0 | 0.1.0 | +| ★ **room-host** | Stores data-room records for rooms it does not govern — "a delivery service, not a community"; speaks Trust Tasks over DIDComm and TSP — *not published* | — | — | 0.1.0 | 0.1.0 | +| **didcomm-test** | DIDComm connectivity test harness — *not published* | 0.6.9 | 0.6.9 | 0.6.9 | 0.6.9 | +| **tests/e2e** | In-process end-to-end harness (MockVta / MockVtc) — *not published* | 0.6.0 | 0.6.0 | 0.6.0 | 0.6.0 | + +`VTI-Dogwood-R1` (2026-09-01) differs from Dogwood by a patch bump on eleven crates (vta-service 0.23.4, vta-sdk 0.32.3, vti-common 0.16.1, pnm 0.14.3, cnm 0.13.3, vti-webauthn 0.2.1, vta-keys 0.4.2, vta-vault 0.5.2, vta-tee 0.2.2, vta-policy 0.3.2, vta-webvh 0.2.3, vta-backup 0.3.2, vta-cli-common 0.12.3); see the Recent Development log for what those carry. (`vti-fuzz` is a nested cargo-fuzz workspace, not a member.) Layering: L0 vti-common / vta-sdk / vti-secrets → L1 keyspaces / config / audit → L2 support / keys / vault / webvh / policy / persona → L3 tee / backup / sweepers → L4 vta-service; the rooms crates hang off vta-sdk + dtg-credentials and are consumed by vta-service, vtc-service, vtc-client and room-host. + +The `vti-didcomm-js` crate (JavaScript DIDComm primitives + spec test vectors) was extracted into its own repository in May 2026 — see [[vti-didcomm-js]]. **Publishing** moved in August 2026 to **release-plz** (#938): merging a PR is no longer releasing — release-plz keeps one `chore: release` PR open, and merging *that* tags each crate (`-v`), generates per-crate CHANGELOGs from conventional commits, runs `cargo-semver-checks`, and publishes via crates.io trusted publishing (GitHub OIDC). **23 of 31 members publish**; the eight internal ones are vtc-service, vta-enclave, vta-mcp, vta-mobile-core, didcomm-test, tests/e2e, room-host and vti-rooms-wasm (`RELEASING.md` still says "20 of 26" — it predates the September crates). Since September the semver report is a *gate*: a Release PR whose bump is smaller than its API change is blocked (#1256), after two exhaustive enums and sixteen wire structs nearly shipped as patch releases — which is why so many wire types became `#[non_exhaustive]` (#1262, #1270, #1271, #1502). Twenty-nine `chore: release` merges landed between Cypress and 2026-09-18. The workspace now states that it **implements the ToIP [VTI specification](https://trustoverip.github.io/dtgwg-vti-spec/)** and that the specification is normative where the two disagree (#1403). ## How It Fits in the Stack @@ -69,15 +74,17 @@ Applications at the top (like [[openvtc|OpenVTC]]) use VTI to manage keys and si ## Dependencies -Key external dependencies: -- `affinidi-tdk` (0.8.5, enforced-authcrypt line) — DID resolution, messaging, data integrity proofs; `affinidi-messaging-delivery` — the reliable outbox layer both services now run on; `affinidi-tsp` -- `trust-tasks-*` 0.9 — the ToIP Trust Tasks document framework every wire operation is expressed in -- `didwebvh-rs` 0.6 — did:webvh operations -- `dtg-credentials` 0.2 — trust graph credential types (DTG Core Credentials WD01) -- `affinidi-mdoc` — ISO mdoc; `regorus` — Rego policy engine -- `fjall` — embedded key-value storage -- `axum` — async HTTP framework -- `ed25519-dalek` 3 / `curve25519-dalek` 5 — Ed25519/X25519 cryptography (SLIP-0010 derivation now in-tree) +Key external dependencies (versions at HEAD, 2026-09-18; Cypress values in brackets): +- `affinidi-tdk` **0.16** [0.8.5] — DID resolution, messaging, data integrity proofs; `affinidi-messaging-delivery` — the reliable outbox layer both services run on; `affinidi-tsp` at the **TSP Rev 3** line (0.2); `affinidi-data-integrity` 0.7 with the `ml-dsa` feature *on* (post-quantum cryptosuites) +- `trust-tasks-*` **0.21** [0.9] — the ToIP Trust Tasks document framework every wire operation is expressed in; `trust-tasks-https`, `-didcomm`, `-tsp`, `-proof`, `-capability-client` bindings +- `didwebvh-rs` **0.7** [0.6] — did:webvh operations +- `dtg-credentials` **0.9.1** [0.2] — trust graph credential types; the data-rooms verifier sits on it +- `affinidi-mdoc` 0.3 / `coset` 0.4 — ISO mdoc; `regorus` 0.12 — Rego policy engine +- `openmls` 0.9 — MLS group custody for data rooms (`vti-rooms`) +- `fjall` 3 — embedded key-value storage +- `axum` 0.8 — async HTTP framework; `governor` — runtime-tunable rate limiting +- `ed25519-dalek` 3 / `curve25519-dalek` 5 — Ed25519/X25519 cryptography (SLIP-0010 derivation in-tree); ML-DSA via `affinidi-secrets-resolver` (`ml-dsa` feature) +- `uniffi` 0.32 [0.28] — the mobile bindings; `aes-gcm` 0.11, `jsonwebtoken` 11, `rustls` 0.23.45 ## Tech Stack @@ -85,20 +92,77 @@ Key external dependencies: - **Async runtime**: Tokio - **HTTP**: Axum 0.8 - **Storage**: fjall (embedded LSM) -- **Crypto**: ed25519-dalek 3, x25519-dalek, p256 -- **Auth**: EdDSA JWTs, DIDComm challenge-response +- **Crypto**: ed25519-dalek 3, x25519-dalek, p256; **ML-DSA-44 / ML-DSA-65** post-quantum signing (since September 2026 — ML-DSA-44 for W3C Quantum-Resistant Data Integrity suites, ML-DSA-65 as TSP Rev 3 §8.1 mandates); MLS (openmls) for data rooms +- **Auth**: EdDSA JWTs, DIDComm / TSP challenge-response; every Trust-Task response is signed and clients verify the signature +- **Supply chain**: Dependabot-managed bumps, every GitHub Action pinned to a commit SHA, least-privilege CI tokens, `cargo deny` across all features ## Recent Development This page is the canonical activity log for the VTI workspace. The [[verifiable-trust-agent|VTA entity]] keeps a focused, VTA-relevant subset. Implementation continues to evolve quickly; treat low-level details as in flux. +The August–September cycle (≈510 commits, PRs #999 → #1561 since the `Cypress` tag on 2026-08-18; 482 of them by one maintainer, 20 by Dependabot) is the fourth consecutive record month, and it produced **two** milestone tags rather than one. **`VTI-Dogwood`** (RC-1 2026-08-29 at #1197; the tag on 2026-08-30 at #1213; a **`VTI-Dogwood-R1`** re-cut on 2026-09-01 at #1218) was a deliberately quiet release — tag-only, no GitHub Release, nothing headline-shaped for a user — because the month it closes was spent making the wire *honest*: every served Trust Task now matches its published schema on both sides, every producer signs, retries are idempotent, replay and freshness are enforced the way the framework says, and errors no longer leak. **`VTI-Eucalyptus-RC-0`** (2026-09-17 at #1553) is the opposite kind of window: two and a half weeks in which the workspace gained five crates, **data rooms**, a **persona store**, **peer vetting**, a **hash-chained VTA audit log**, **post-quantum (ML-DSA) keys and hybrid credentials**, and a **TSP Rev 3** stack with persisted relationships and multi-hop metadata privacy. Dependencies moved just as hard — trust-tasks-rs 0.9 → 0.21, affinidi-tdk 0.8 → 0.16, dtg-credentials 0.2 → 0.9 — and the wire types they carry are now generated from the published specs rather than hand-written. The direction of travel: the VTA is becoming the holder's *whole* agent (identity, memory, application state, rooms), and the workspace now declares itself an implementation of the normative ToIP VTI specification. + +### Dogwood — 2026-08-30 — the silent release: conformance, idempotency, honesty on the wire + +The `VTI-Dogwood` tag sits on #1213 (a VTC feature commit, not a release merge — the versions were cut at RC-1's #1197 and rolled forward by four `chore: release` merges in between). Snapshot versions: vta-service **0.23.3**, vta-sdk **0.32.2**, vti-common 0.16.0, vti-secrets 0.3.1, vtc-client 0.5.1, pnm 0.14.2, cnm 0.13.2, vta-keys 0.4.1, vta-vault 0.5.1; deps trust-tasks-rs 0.17, affinidi-tdk 0.10, dtg-credentials 0.4, jsonwebtoken 11, aes-gcm 0.11. The tag carries only the description "VTI Dogwood"; there is no GitHub Release and the word "Dogwood" appears nowhere in the repo's docs — it exists for the other repos' pins ([[coordinated-releases]]). What the twelve days between Cypress and Dogwood actually bought: + +- **The conformance sweep — drift reaches zero (#1076 → #1115, 08-24..08-26).** A new test layer observes the *real* responses each handler emits and validates them against the published Trust-Task schema, rather than trusting fixtures ("a false green, and the fixture that caused it", #1100). The VTC started at 33 drift entries and the sweep took it to zero in two days (#1093–#1112: camelCase envelopes, nested response wrappers, the endorsement model the spec defines, `issued` is a credential receipt not a timestamp; `recognise`/`submit` moved to 0.2 — "drift reaches zero", #1105). The VTA side found three of four response defects the same way (#1113–#1116), gated response conformance in CI and measured task coverage: **102 of 109 dispatched specs** exercised end-to-end, with the last seven named and explained (#1151). A "produced-URI census" now checks the URIs the services *send*, not only those they serve (#1181). Design notes: `trust-task-envelope-conformance.md`. +- **Framework 0.5.0 enforced at the dispatch spine.** The VTA had enforced none of the four checks a Trust-Task spec declares for itself; #1146 (breaking) enforces all of them — `recipient` on all 109 dispatched specs, `proof` on 72, audience binding, `issuedAt` on 70 — keyed by Type URI through `spec_policy_for`, which was authored upstream for this. Freshness bounds (#1117), the framework's `ReplayGuard` + `FreshnessPolicy` replacing the hand-rolled pair — the acceptance window and the replay record's retention are the *same* bound, and the service had a ten-minute record against an unbounded window "believing it had a replay defence" (#1126, #1127) — and **error messages stop being a probing oracle**: internal causes no longer leak into `message`, `details` and audit reasons are bounded (#1130–#1132). Auth `revoke-session` no longer tells a stranger the session exists (#1141). +- **Retry and idempotency — who owns what (#1009 → #1014, `retry-and-idempotency.md`).** The dangerous case is a processed request whose *reply* was lost: a naive retry of `webvh/dids/create` mints a second DID and orphans the first. The VTA dedups keyed Trust Tasks on an `idempotencyKey` (#1011, breaking) and the SDK holds one key across every attempt of an operation via `VtaClient::idempotent` (#1012); every task is classified by what a lost reply costs (#1010) and the contract is proven by counting effects, not statuses (#1013). +- **Every client has an identity, and every producer signs.** #1146 made the VTA refuse unsigned tasks and left seven production client paths that could not sign; #1147 (breaking) replaced the three-step `new` + `with_identity` + `set_token` with `VtaClient::authenticated(url, identity, token)` and adopted `provision/integration` 0.3. Then the restriction that only a `did:key` could sign a Trust Task — never a policy, just the shape of two helpers, and the reason a provisioned did:webvh integration could dispatch none of the 210 proof-requiring tasks — was lifted: **any DID that names a key may sign** (#1193, breaking). The consent approve-request pushed to an approver's phone is signed too (#1180). +- **Retire on evidence, not by hand.** `vta/discovery/capabilities` was retired in favour of the canonical `trust-task-discovery/0.1` family (#1042, #1044); the `vta/services` task family absorbed twenty `/services/*` REST routes with a drain guard so a mediator teardown cannot discard in-flight messages (#1017); and the superseded-route table gained a guard so a deleted route cannot sit at "zero usage" forever (#1047). A **third store — versioned, namespaced application state** — arrived as `vta/app-state/*` beside the secrets and credential vaults (#1041, #1051). The audit destination became a deployment choice (#1049): a seam for a stronger sink, which September then filled (below). A design note for Trust-Task **version negotiation** (#1046) records that negotiation is tractable only if versions carry a compatibility contract — a proposal for the shared registry, not yet implemented. +- **VTC — DTG conformance.** Trust Task Context Binding is *enforced* on presented credentials (#1178, breaking — `taskContext` had zero occurrences, and its absence permits the spec's "context collapse"); the digest binding on presented witness credentials is verified (#1161); **personhood** runs over messaging with a challenge bound to something signed (#1086), a community can vet a member in person and have it count as personhood evidence (#1085), the two DTG personhood-conformance gaps closed (#1089); the relationships graph distinguishes **half-edges from complete edges** (#1073), a member can publish a VRC without naming their membership DID in it — R-DIDs as the spec recommends (#1061, breaking); the VPC lands as the deliberate-correlation mechanism on an edge (#1074); publishing is authenticated by the document's own proof (#1088); credentials past their validity window are rejected at ingress (#1075); and — the commit the tag sits on — **the membership edge is completed and drawn on the trust graph** (#1213): a VMC pair is an edge like a VRC pair, and the VTC now keeps the credentials it issues so it can prove the digest binding. The console's wire types are generated from the daemon (#1188). +- **Operational fixes worth knowing.** DID deletion cascades, refuses or revokes across ACL entries, issued credentials, sessions and per-DID state, with a dry-run that shows what would be destroyed (#1198/#1199, `did-deletion-cascade.md`); a VTC ACL revoke can no longer orphan a member row (#1194/#1196 — found in production); `vta-mcp` compiles a session store in so session mode works at all (#1091) and gains a local operation guard + per-call log (#1101); TEE bootstrap 410 / vsock ENOTCONN (#1003); `CI` caches keyed on `Cargo.lock` were restoring stale `target/` for path-dependency changes across seven jobs (#1134). +- **Dependencies (breaking, mostly).** trust-tasks-rs 0.9 → 0.11 (#1015) → 0.12 (#1121) → 0.17 (#1144, #1176, #1182) — each step pulling registry-moved error codes, `keyId` from the wire, `ext` on every payload (#1231); `credentials/issue` 0.2, vault 0.3, `device/wipe` 0.2 (#1145, #1159); affinidi-tdk 0.8 → 0.10; dtg-credentials 0.2 → 0.3 (#1207) → 0.4; aes-gcm 0.11 (nonce conversions no longer panic, #1173), jsonwebtoken 11, tower-http 0.7, azure_identity 1.0, k8s-openapi 0.28; the legacy rustls tree dropped (#1158); "bring every dependency to latest" (#1055); **Dependabot** configured (#1160). `AppState`, `AppStateParts` and the growth-prone wire bodies became `#[non_exhaustive]` so adding a field stops being a break (#1024, #1057). + +### Dogwood-R1 — 2026-09-01 — the re-cut + +`VTI-Dogwood-R1` is Dogwood plus one `chore: release` (#1218) carrying **five fixes and five dependency moves** — 51 files. The fixes are all "the VTA said the wrong thing": device list / disable / wipe are scoped to the caller's contexts (#1217); not-found, conflict and gone stay typed across the Trust-Task boundary instead of collapsing to an internal error (#1219); provisioning says *which side* is out of date and checks authorization before minting (#1220); approver sets resolve from one row-first source rather than three (#1221); the VTC tells a member which side is out of date instead of "unknown verb" (#1223). The dependency moves: uniffi 0.28 → 0.32 (mobile bindings), affinidi-tdk 0.11 with affinidi-mdoc 0.3 / coset 0.4 moved together (#1230), regorus 0.11, base64 0.23. **Not** the reason for R1: the "VTC setup began failing after Dogwood" regression fixed by #1560 on 2026-09-18 — that defect was introduced by #1483 on 09-15 (the unified webvh transport selector reading a compile-time TSP constant while the DIDComm handler holds no TSP socket) and belongs to the Eucalyptus window, after both Dogwood tags. + +### Trust Tasks over TSP Rev 3 — relationships, one outbound path, multi-hop privacy — 2026-09-15 → 09-18 + +The largest post-Dogwood stream, and the one that reverses a design doubt. `tsp-vs-didcomm-trust-task-surface.md` was written to ask whether the §7.2.2 relationship layer was worth its cost; the answer, recorded as **decided**, is that multi-hop TSP — routing where intermediaries never learn the final recipient — is a *requirement*, so the recovery work is "justified investment, not speculative rent". What landed: **Rev 3 adopted** (#1512, breaking — a flag day, since nothing a Rev 2 peer packs can be unpacked by a Rev 3 one; inventory in `tsp-rev3-migration.md`); Trust Tasks carried in the **TSP binding envelope** (#1478, #1488); **one outbound path** chosen from what a peer advertises, replacing four hand-rolled carriages (room-host REST, webvh DIDComm, eight webvh verbs, device push — #1474), with the webvh selector folded onto it so a TSP did-host is reached over TSP (#1483) and a did-host advertising where it accepts Trust Tasks (#1476); replies correlated in the spine so TSP can *initiate* (#1482); TSP relationships **persisted across restarts** (#1531), formed before a send or a health ping (#1527, #1537, #1540), answered instead of ignored (#1525), re-formed on a reply timeout on the client (D4, #1544), the VTC registry client (#1546) and the VTA's server-initiated sends via a single-flight `RecoveryCoordinator` (D6, #1549), with boot enumeration + idle eviction (#1534) and relationship-gate drops surfaced as telemetry (D8, #1536); and — after the RC — **cross-mediator sends nested for metadata privacy** (#1559): our mediator never learns the recipient. One TSP transport, and it must carry a mediator (#1507). The `tsp-relationship-recovery.md` note tracks D1–D9; D7/D9 hardening remain open. #1560 (breaking) then fixed the transport seam to select by the sender's *live* capability rather than the build feature — the post-Dogwood VTC-setup regression. + +### Post-quantum: ML-DSA keys, hybrid credentials, did-templates 3.0 — 2026-09-16 → 09-18 + +`KeyType` gains **`MlDsa44` and `MlDsa65`** (#1502, breaking via `#[non_exhaustive]`) — two parameter sets because two specs require different ones: W3C Quantum-Resistant Cryptosuites define Data Integrity suites for ML-DSA-44, TSP Rev 3 §8.1 mandates ML-DSA-65. Keys derive from the BIP-32 chain (#1505); a derived key **carries the algorithm it was minted with** rather than a literal at every save site (#1532, breaking); an operator can create one and see which axis it protects (#1535). did-templates gain a `keys` block declaring which algorithms a template's slots use (#1530, breaking), a third slot beyond the classical pair (#1554 — the previous rejection's own advice would have published a `PLACEHOLDER-NEVER-SUBSTITUTED` literal into a write-once did:webvh log), and the VTA accepts **did-templates 3.0 alongside 2.0** (#1538) with clients carrying a post-quantum template (#1542). On the VTC: verification reads a document carrying *more than one proof* (#1548, #1550), and the RC commit itself (#1553) has **the VTC sign with every key it holds** — one proof per key, each under its own cryptosuite, byte-identical output for a single-key VTC so nothing downstream changes until a second key exists. Its most consequential finding: `affinidi-data-integrity` was pinned without its `ml-dsa` feature, so every capability built above it led to a key that could not sign — "built, tested, and unreachable". Post-RC: a sealed-transfer template-bootstrap variant carrying a second signing key (#1556) and a VTC provisioned from the v2 template issuing hybrid credentials (#1557). + +### Security sprint — audit chain, domain-separated oracle, exportability, endpoint vetting, CI supply chain — 2026-09-10 → 09-12 + +Nine `security(...)` commits and the audit work, most closing requirements of the VTI specification's divergence register. **The VTA's audit log is now a hash chain** (`vta-audit-chaining.md`, #1408; #1412 → #1421): its own audit key from the OS random source, deliberately *not* seed-derived so a mnemonic holder cannot recompute it (#1419); each entry commits to its predecessor, with actors and DID-shaped targets under a keyed hash beside the plaintext so an erasure can null the plaintext while the chain still verifies (#1420); a `verify` pass that separates the counts a bare pass/fail hides (#1421); pre-chain keyspaces served (#1413); and failed audit writes *reported* rather than `let _ =`-swallowed at 27 call sites, plus the signing oracle finally leaving a `keys.sign` row (#1443, breaking — `sign_payload` takes a required audit sink). The **opaque signing oracle is domain-separated** (#1417, breaking): unparseable payloads are framed under a domain tag so a signature obtained for one purpose cannot verify as an assertion, token or proof in another protocol. A key can be marked **never leaving the VTA** (`exportable`, #1401, #1407), and `keys/export-secret` replaces the misnamed, unspecced, global-Admin-gated `seeds/export-mnemonic` (#1404). The SDK **vets DID-advertised VTA endpoints** (private, loopback, link-local, CGNAT … refused) and binds cached tokens to their origin (#1436, breaking); the resolver refuses did:webvh resolution to non-public hosts by default (#1448, breaking); cnm checks who sealed an admin credential before installing it (#1437); backup exports are written 0600 with `create_new` (#1438); the enclave proxy resolves `nitro-cli` by absolute path with a scrubbed environment (#1441); `/auth/challenge` no longer discloses enrolment (#1405); a plugin icon renders as an image, not markup (#1442). CI: **every action pinned to a full commit SHA** — 58 refs, none pinned, and the log showed a `@stable` ref drifting within nine days on a job holding `id-token: write` (#1440); workflow inputs validated through env (#1434); rustls 0.23.45 for RUSTSEC-2026-0285 (#1468). + +### Authorization surface — ACL capabilities, persona-holder, memory grants, rate limits — September 2026 + +`AclEntry::capabilities` was stored, echoed and **enforced nowhere**; now an entry narrows within its role and can never widen beyond it, operators can set it, and entries are created already narrowed (#1279, #1280, breaking). A `persona-holder` capability grants authority over the holder's own identity without granting every context — the case OpenVTC's context-scoped admin found (#1286) — and `whoami` reports the capabilities it enforces (#1298). Agent memory gets `MemoryRead`/`MemoryWrite` so read-only grants exist server-side rather than as an MCP-side glob (#1234), with `pnm memory` (#1222). Provisioning can ask for an unrestricted admin and is told what it got (#1303); pnm clients self-provision their mediator ACL (#1292); a step-up can record a bound approval without elevating the session (#1316). **Rate limiting** (`rate-limiting.md`): `did.jsonl` gets its own limiter so a self-hosted DID's log fetch cannot starve auth, and every VTA/VTC 429 says which limiter refused (#1510, #1514, breaking); quotas are tunable at runtime (#1519); refusals are typed in the SDK and CLI (#1511, #1521). Seed reads are **cached** so KMS traffic scales with time, not requests — prompted by a 58.1M-request Secrets Manager line item on a VTA doing ~22 req/s (#1290, breaking). + +### Signed responses, verified replies, and the mock that had to learn to sign — 2026-09-09 + +265 specs require a signed success response and none got one: both services now sign success responses (#1334, #1335), and — the surface that matters — **`VtaClient` verifies every reply it receives** (#1341), because "a reply is bytes off a socket" and without the proof an intermediary can rewrite an ACL listing or flip a policy decision. `MockVta` had to learn to sign (#1350, #1355), the Trust-Task proof verifier moved down from vti-common into the SDK (#1340) and resolves did:peer with no I/O (#1364), and a Trust-Task proof can be verified against the typed document (#1500). The mobile core verifies the *consent* prompt, not only the step-up one (#1324 — a document from anyone rendered identically to a real one). + +### New crates: data rooms, persona, vetting — September 2026 + +Covered in depth on their own pages as they land; the shape here. **Data rooms** (`docs/02-vta/data-rooms.md`, `docs/05-design-notes/data-rooms*.md`, two security reviews) — a shared record space "readable and writable by exactly the parties its credentials admit, for the humans in it and for their AI agents": a room has its own DID, issues its own credentials, keeps its group key under MLS, and can move between hosts without reissuing a credential. #1237 landed storage, dispatch, verification, MLS and a host in one day (09-03); the following week added lifecycle where "the host never decides" (#1242), audit without learning who (#1244), group custody and succession (#1248, #1251), the presentation oracle so an agent never holds its human's credentials (#1247), read mirrors (#1289), a Merkle record commitment and tree heads (#1346, #1375), epoch anchoring where a host cannot reach it (#1391), a browser member via `vti-rooms-wasm` (#1347, #1382), and a VTC console for the rooms a community hosts (#1325, #1330). **`vta-persona`** (#1255 → #1342) — the holder's own attributes, the profiles that project over them, bindings to persona DIDs, disclosure history — a one-way boundary a context cannot read across, `release: stepUp` enforced on disclosure (#1304), a deployment-declared claim-type registry (#1315, #1327), and `persona/facet` (#1338); it moved to trust-tasks-rs 0.18 (#1266). **Peer identity vetting** (`docs/03-vtc/vetting.md`, #1425 → #1432, #1439): an applicant is vetted by existing members who sign Vetting Statements the community counts in the join decision — a vetter role credential, a vetter registry with automatic grants, community branding in the manifest, cnm admin commands and a PGP web-of-trust vetter bootstrap; the wire types are generated from the published specs. Also new: `docs/02-vta/vta-mcp.md` and an agent-memory-with-MCP example. + +### VTC, webvh, backup, TEE, mobile — September 2026 + +- **VTC**: a failed registry-sync job is visible and recoverable (#1487, #1493) and the registry mirror can be compared against the registry (#1489); the recognition graph is browsable from either side (#1495); Trust Tasks accepted in the DIDComm binding envelope with the proof verified in the spine "where the bytes still exist" (#1497, #1501); rooms tasks routed by payload type (#1503); the admin UI authors vetter-eligibility policy, admission criteria and statement types, charts what a policy *decides* by evaluating it, and lets the join dry-run reach the vetting routes (#1465–#1473); `build.rs` stops writing to the source tree (#1254). `vtc-client`: **any DID method can hold**, and a session carries it (#1399). +- **did:webvh**: key records follow the document's verification-method ids, with a repair (#1466); realign-keys as a Trust Task (#1470); `versionTime` clamped against the previous entry (#1456); refuse to delete a DID whose host is unregistered (#1518); `addTspService` honoured on a template-rendered DID (#1424); each DID resolved once per process through a shared resolver (#1517); pre-rotation can be turned on for a DID that never had it (#1203). +- **Backup**: a **DIDComm/TSP-only VTA can be backed up** via the `chunkedTrustTask` algorithm — `backup/get-chunk`, `put-chunk`, `initiate-*` 1.1 (#1522, breaking; trust-tasks-rs 0.21.1 was cut for it), with typed refusals and the password validated at import too (#1516, #1484). +- **TEE / Nitro**: `allow_kms_reinit` is explicit authorization regardless of KMS failure class, and an authorised reinit clears the integrity manifest so the result actually boots (#1249); pnm anchors a TEE bootstrap connect **by DID and pinned PCR0** with mutually exclusive `--vta-did` / `--vta-url` (#1454); the Nitro image copies every workspace member (#1361); aws-smithy-types bounded (#1485). +- **Mobile**: uniffi 0.32, verified consent prompts (#1324), Rev 3 TSP sealing (#1488) — `vta-mobile-core`'s manifest stays at 0.6.18 because it is unpublished. +- **CLI**: removal commands are named `delete` and say what a delete leaves behind (#1513); `vta remove` confirms unless `--force` (#1457); persona commands read in the words a person uses (#1291). + +### Dependency and release-engineering moves — September 2026 + +trust-tasks-rs 0.17 → 0.18 (#1266) → 0.20.x (exportability, export-secret) → **0.21.3** (chunked backup, did-templates 3.0); affinidi-tdk 0.11 → **0.16** (carrying affinidi-tsp 0.2 / Rev 3); dtg-credentials 0.4 → 0.6 → **0.9.1** (#1356 — a presentation is bound to its presenter); didwebvh-rs 0.7; regorus 0.12 (#1295); getrandom 0.4, ulid 3, chacha20poly1305 0.11, jsonschema 0.55, dirs 7; `rand` held at 0.8 while rPGP needs it (#1450). Release engineering: the semver report became a gate (#1256) and grew memory, time and a fallback for unbuildable baselines (#1491, #1492, #1496, #1520); a release baseline was moved off an unresolvable commit and 18 crates unstuck (#1406, #1414); the newest push wins Release-PR regeneration (#1453); `Cargo.lock` must satisfy the manifests (#1302) and members must agree on sibling versions (#1409); Test split into workspace/VTC halves gated on what changed (#1265, #1267); every crate has a README because a missing one blocked a publish (#1272); every documented build passes `--locked` (#1481); root design notes relocated into `docs/` (#1541); `RELEASING.md` says what actually counts as a breaking change (#1264). + The July–August cycle (≈340 commits, PRs #625 → #1008 since 2026-07-06) is the VTI's third consecutive record month, and it ends in the coordinated **`Cypress`** release ([[coordinated-releases]]) — the first tree-named milestone to go through formal release candidates (`VTI-Cypress-RC-0` 08-02, `VTI-Cypress-RC-1` 08-11) and the first cut as a crates.io-published snapshot under the new release-plz process. Thematically the month was about *converging*: every wire operation folded onto the canonical ToIP **Trust Tasks** registry URIs; the three approval mechanisms collapsed into **one approvals model**; the `vta-service` monolith was **decomposed** into eleven subsystem crates; and TSP went from opt-in feature to a *selectable* transport a VTA can run without DIDComm at all. New capabilities arrived too — **ISO mdoc** receive/present, **non-extractable internal keys**, a hardened non-TEE mode, Nitro tenant config over vsock — but the dominant signal is a codebase preparing for a 1.0 shape. ### Cypress — 2026-08-17 — coordinated release, release-plz, RC process The annotated `Cypress` tag points at a release-plz `chore: release` merge (#997). Snapshot versions are in the Components table above (vta-service **0.17.0**, vta-sdk **0.25.0**, vtc-service 0.11.58, vti-common 0.12.1, pnm 0.12.6, cnm 0.11.22, vta-mobile-core 0.6.18). Versions at the start of the window for comparison: vta-sdk 0.18.17, vta-service 0.10.23, vtc-service 0.10.13. Two RCs preceded it: RC-0 at #893 (keys import canonical on every transport, 08-02) and RC-1 at #935 (changelog sign-posting, 08-11) — `Banyan → Cypress` is simply the next letter in the tree-named sequence, but the RC discipline is new. -**release-plz (#938, 08-12).** Replaces the hand-rolled `publish.yml` / `cut-release.sh` / version-bump guards and the `changelog.d/` fragments introduced only two weeks earlier (#878): one open Release PR, per-crate tags, git-cliff CHANGELOGs, `cargo-semver-checks`-derived bumps, trusted publishing (the workflow had to keep the name `publish.yml` because crates.io pins the filename, #943). #938 first cut the published set 21 → 7; #962 (08-13) reversed most of that because `openvtc-core` dev-depends on `vta-service` for `MockVta` and `vti-common` re-exports `vta_sdk::acl` types — a frozen vta-service stopped compiling against a moving vti-common. Current rule (`RELEASING.md`): 20 of 26 publish. Fourteen `chore: release` merges landed between #938 and HEAD. Also: a plain non-TEE `Dockerfile` + CI (#955), the Nitro image cached with cargo-chef and built in CI (#956), CI diet (#961), a DCO exemption for verified org-member commits so release merges can pass (#944), bounded CI jobs (#1002, #1006). +**release-plz (#938, 08-12).** Replaces the hand-rolled `publish.yml` / `cut-release.sh` / version-bump guards and the `changelog.d/` fragments introduced only two weeks earlier (#878): one open Release PR, per-crate tags, git-cliff CHANGELOGs, `cargo-semver-checks`-derived bumps, trusted publishing (the workflow had to keep the name `publish.yml` because crates.io pins the filename, #943). #938 first cut the published set 21 → 7; #962 (08-13) reversed most of that because `openvtc-core` dev-depends on `vta-service` for `MockVta` and `vti-common` re-exports `vta_sdk::acl` types — a frozen vta-service stopped compiling against a moving vti-common. The rule at the time (`RELEASING.md`): 20 of 26 publish — 23 of 31 as of September. Fourteen `chore: release` merges landed between #938 and Cypress. Also: a plain non-TEE `Dockerfile` + CI (#955), the Nitro image cached with cargo-chef and built in CI (#956), CI diet (#961), a DCO exemption for verified org-member commits so release merges can pass (#944), bounded CI jobs (#1002, #1006). ### Trust-Task canonicalisation — everything folds onto `trusttasks.org/spec/*` — July–August 2026 diff --git a/content/entities/vta-browser-plugin.md b/content/entities/vta-browser-plugin.md index 00e3c0f..2ffe22b 100644 --- a/content/entities/vta-browser-plugin.md +++ b/content/entities/vta-browser-plugin.md @@ -1,8 +1,8 @@ --- title: "vta-browser-plugin — the VTA Wallet in the browser" type: entity -tags: [browser, wallet, pnm, webauthn, passkeys, siopv2, login, secondary] -date-updated: 2026-08-19 +tags: [browser, wallet, pnm, webauthn, passkeys, siopv2, login, tsp, management-console, persona, rooms, security, secondary] +date-updated: 2026-09-18 repo: https://github.com/OpenVTC/vta-browser-plugin --- @@ -10,40 +10,55 @@ repo: https://github.com/OpenVTC/vta-browser-plugin *Repo: [github.com/OpenVTC/vta-browser-plugin](https://github.com/OpenVTC/vta-browser-plugin) — npm scope `@openvtc/pnm-*`* -Passkeys solve *local* authentication; DIDs solve *global* identity; neither alone lets a person log into a third-party website **as the controller of their VTA-held DID**. The browser plugin is the bridge: it proves control of a DID hosted in a remote [[verifiable-trust-agent|VTA]] by performing a passkey ceremony in the browser — no DID private key ever leaves the VTA, and no long-lived bearer token sits in browser storage. Its first milestone (May 2026) enrolled a passkey as an `authentication` verification method in the VTA's [[did-webvh|did:webvh]] document, so any relying party can verify a WebAuthn assertion by DID resolution alone. +Passkeys solve *local* authentication; DIDs solve *global* identity; neither alone lets a person log into a third-party website **as the controller of their VTA-held DID**. The browser plugin is the bridge: it proves control of a DID hosted in a remote [[verifiable-trust-agent|VTA]] by performing a passkey ceremony in the browser — no DID private key ever leaves the VTA, and no long-lived bearer token sits in browser storage. Its first milestone (May 2026) enrolled a passkey as an `authentication` verification method in the VTA's [[did-webvh|did:webvh]] document, so any relying party can verify a WebAuthn assertion by DID resolution alone (the server half of that idea is VTI's `vti-webauthn` crate, a DID-VM-resolved assertion verifier). -Since then it has grown into the **VTA Wallet**: the browser-resident *Personal Network Manager* (see [[vta-topology]]) that holds holder identities, keeps a persistent mediator inbound session, and renders **consent / step-up approvals** for VTA-gated operations — the in-browser sibling of the iOS mobile agent. +Since then it has grown into three things in one MV3 extension. The **VTA Wallet**: the browser-resident *Personal Network Manager* (see [[vta-topology]]) that holds holder identities, keeps one persistent mediator inbound session *per onboarded agent*, and renders **consent / step-up approvals** for VTA-gated operations — the in-browser sibling of the iOS mobile agent. The **management console** (September 2026): everything an operator used to do to their agent from the `pnm` CLI — contexts, keys, DIDs, ACL, approvals, policy, transports, audit — now reachable from a browser tab, composed in the console and signed in the wallet's offscreen document so the console itself holds no key material. And the **persona identity map** and **data rooms** panes, the first user-facing surfaces for the VTA's persona and rooms task families. + +*Not to be confused with `pnm-server`:* that is the headless build of VTI's Rust `pnm` CLI (secrets in plaintext config rather than an OS keyring), downloaded from `download.firstperson.dev/pnm-server/…` in the [[vti-setup]] guides. Nothing in this repo produces it. ## Components -npm-workspaces monorepo (Node ≥ 24): +npm-workspaces monorepo (Node ≥ 24). Versions at the coordinated-release tags: -| Package | Version | Role | -|---------|---------|------| -| **`@openvtc/pnm-core`** | 0.4.0 | The library: WebAuthn ceremony + COSE→Multikey, DID verification-method builder, a `VtaTransport` interface with REST and DIDComm implementations, `WalletSession.bootstrap()` (mint/load a did:key → did:peer:2 holder, coordinate-mediation/2.0 enrolment, pickup/3.0 live delivery), SIOPv2 self-issued `id_token`, RP login + step-up, vault, provisioning, Trust Tasks, inbound handling (persist-before-ack, reconnect scheduler). Since #121 (Aug 2026) a standalone, strictly layered library with operator surfaces `/admin` (acl/keys/policy), `/did-hosting` (23 `did-management/*` tasks against [[affinidi-webvh-service|did-hosting-service]]) and `/vtc` (apply / track / hold / leave), built on generated `@openvtc/trust-tasks` 0.9 bindings — heading for its own repo as a general-purpose VTA client library. | -| **`@openvtc/pnm-extension`** | 0.2.0 | MV3 Chrome extension (popup, options, offscreen inbound session, consent windows). Injects `window.vtaWallet` into *granted* origins: `login`, `loginDidcomm`, `proxyLogin`, `signTrustTask`, `requestTask`, `stepUpVta`, `vaultList`, `apiGet/apiPost`, `mediatorStatus`. Optional host permissions only, no static content scripts, no `cookies` permission (CI-asserted). Chrome Web Store packaging since #119. | -| **`@openvtc/vti-tsp-js`** | 0.2.0 | Pure-TypeScript [[trust-spanning-protocol|TSP]], byte-compatible with `affinidi-tsp`: HPKE / Ed25519 / X25519 via @noble (no WASM, runs in React Native), CESR framing, Direct / Nested / Routed modes; RFC 9180 vectors in CI. | -| `@openvtc/pnm-pwa`, `pnm-demo-rp`, `pnm-reviewer-demo` | 0.2.0 / 0.1.0 | A Vite + React wallet PWA, a demo relying party, and a Web Store reviewer bootstrap. | +| Package | Cypress (08-17) | VTI-Dogwood (08-31) | VTI-Dogwood-R1 (09-01) | main (09-18) | Role | +|---------|------|------|------|------|------| +| **`@openvtc/pnm-core`** | 0.4.0 | 0.6.0 | 0.6.0 | **0.9.1** | The library — a standalone, strictly layered VTA client. `WalletSession.bootstrap()`, WebAuthn ceremony + COSE→Multikey, SIOPv2, RP login (three shapes, below), vault, provisioning, Trust Tasks, inbound handling (persist-before-ack, reconnect scheduler), the three transport channels (REST / DIDComm / TSP). Since 0.6.0 **every channel signs every outbound Trust-Task document** and takes a `SigningIdentity | TaskSigner` as a *required* input; since 0.7.0 every task URI and wire type comes from the generated `@openvtc/trust-tasks` bindings (`task-surface.json` tracks vta-sdk 0.33.0: 226 task URIs, all implemented). Subpaths, deliberately absent from the root barrel: `/admin` (acl incl. capability narrowing, keys, policy, backup-abort, services, the holder's half of `persona/*`), `/did-hosting` (23 `did-management/*` tasks against [[affinidi-webvh-service|did-hosting-service]]), `/vtc` (apply / track / hold / leave), `/persona` (the wallet's half: disclosure gate, contacts, bindings, claim-type resolution), `/rooms` (data-rooms client). Runtime deps: `@openvtc/vti-didcomm-js` `^0.10.0`, `@openvtc/vti-tsp-js` `^0.3.0`, `@openvtc/trust-tasks` `^0.19.5`, `@cfworker/json-schema` (full 2020-12 validation of inbound payloads). | +| **`@openvtc/pnm-extension`** | 0.2.0 | 0.2.0 | 0.2.0 | 0.2.0 | MV3 Chrome extension: popup, options/setup, offscreen inbound sessions, consent windows, and since #155 `manager.html` — the management console, built as its *own* Vite bundle (`codeSplitting: false`) so CI can assert that `admin/*` task URIs appear in `manager.js` and nowhere else, and that `vta/seeds/*` (mnemonic export) appears nowhere at all. Injects `window.vtaWallet` into *granted* origins: `login`, `loginDidcomm`, `proxyLogin`, `walletProfile`, `walletDefaults`, `signTrustTask`, `requestTask`, `stepUpVta`, `vaultList`, `apiGet/apiPost`, `mediatorStatus`. Optional host permissions only, no static content scripts, no `cookies` permission (CI-asserted). Chrome Web Store packaging exists (#119) but **the extension has never been submitted or published** — a fact the repo's CLAUDE.md turns into a rule: nothing is deployed, so no compatibility folds. | +| **`@openvtc/vti-tsp-js`** | 0.2.0 | 0.2.0 | 0.2.0 | **0.3.0** | Pure-TypeScript [[trust-spanning-protocol|TSP]]: HPKE / Ed25519 / X25519 via @noble (no WASM), CESR framing, Direct / Nested / Routed modes, RFC 9180 vectors in CI. 0.3.0 (#253, 2026-09-15) is **spec Rev 3**: packs Rev 3 only, reads Rev 3 *and* Rev 2 via a frozen decode-only codec; adds relationship control messages (XRFI/XRFA/cancel), the §7.2/7.3 state machine, and — from an external contributor (Mickens-Lab, #233) — pluggable `SigningKey` / `KeyAgreement` for non-exporting key custody. | +| `@openvtc/pnm-pwa` | 0.2.0 | 0.2.0 | 0.2.0 | 0.2.0 | Vite + React wallet PWA with the `/smokes` diagnostic page. | +| `@openvtc/pnm-demo-rp`, `pnm-reviewer-demo` | 0.1.0 | 0.1.0 | 0.1.0 | 0.1.0 | A test website exercising every wallet login path (#140), and a Web Store reviewer bootstrap. | ## How the Web Login Family Fits Together ``` -web page ──window.vtaWallet.login / proxyLogin / requestTask──▶ extension - extension ──▶ @openvtc/pnm-core (SIOPv2, Trust-Task envelopes, VtaTransport) - ──▶ @openvtc/vti-didcomm-js (authcrypt + routing/2.0 forward + mediator WS) ─┐ - ──▶ @openvtc/vti-tsp-js (TSP frames) ─┼──▶ VTA - ──▶ REST (bootstrap only) ─┘ -RP server ──▶ @openvtc/rp-sdk verifies the id_token / confirm response by resolving the holder DID +web page ──window.vtaWallet.{login | proxyLogin | walletProfile | requestTask}──▶ extension + extension ──▶ @openvtc/pnm-core (SIOPv2 minter, Trust-Task envelopes, channel signs every document) + ──▶ @openvtc/vti-tsp-js (TSP Rev 3 frames; Trust Task inside the tsp/0.1 binding envelope) ─┐ + ──▶ @openvtc/vti-didcomm-js (authcrypt + routing/2.0 forward + mediator WS; net-guard vets URLs) ─┼──▶ VTA + ──▶ REST (bearer; net-guard vets the base URL) ─┘ + ◀── every reply's Data Integrity proof verified, and its signer checked against the agent addressed (#215) +RP server ──▶ @openvtc/rp-sdk verifies the id_token / confirm response by resolving the holder or persona DID +manager.html ──RUNTIME_MANAGER_TASK {type, payload}──▶ offscreen mints + signs ──▶ same channels (console holds no key) ``` -Transport preference is TSP > DIDComm > REST (July 2026). DIDComm goes through [[vti-didcomm-js]] (pinned `^0.6.2` as a *correctness floor*): inner authcrypt holder→VTA, wrapped in `routing/2.0/forward`, outer anoncrypt to the mediator over WSS. Three login shapes: **self-issued SIOPv2** (`login` — the wallet self-issues an EdDSA `id_token` with its did:key holder, nonce from the RP's `/auth/challenge`, wrapped in an `auth/authenticate/0.1` Trust-Task envelope; the RP verifies with [[rp-sdk-js]]); **VTA-proxied login** (`proxyLogin` — `vault/proxy-login/0.1`, the VTA mints the `id_token` with a vault-held key so the long-term key never leaves the VTA); **DIDComm login** to a did-hosting relying party (`loginDidcomm`). The early "VTA performs the password login and the wallet injects the cookie jar" variant was removed in #121. +Transport preference is TSP > DIDComm > REST, but since #143 the UI reports the transport that *actually carried a byte* ("advertisement is not availability"): a mediator that refuses the extension's origin at its CORS-governed auth handshake silently takes out TSP and DIDComm together, and the wallet's self-test now infers that refusal rather than blaming the network. Both `vti-didcomm-js` floors are correctness constraints, not preferences: `^0.6.2` for hand-off-then-ack (R1.6) and `^0.10.0` for reading long-framed (`--E`) TSP replies, without which any TSP reply over ~12 KB — a full `keys/list`, say — vanishes unacked forever. + +Three login shapes, all of which now sign in as a **per-site persona** rather than the wallet's own holder DID (#144–#146, VTI-Dogwood): **self-issued SIOPv2** (`login` — the wallet resolves the vault entry bound to the browser-attested origin, asks once on first use, and mints an EdDSA `id_token`; the holder's own identity is offered last, labelled with its linkability cost; the RP verifies with [[rp-sdk-js]]); **VTA-proxied login** (`proxyLogin({})` now resolve-or-bind; `vault/proxy-login/0.1` mints the token with a vault-held key that never leaves the VTA; `walletProfile({target})` answers "who does this site know me as?" without enumerating the vault); **Trust-Task login** (`loginDidcomm`, since #141 the canonical `auth/challenge/0.1` → `auth/authenticate/0.1` pair over *whichever* transport the RP advertises, the document signed as the persona by `vault/sign-trust-task/0.2` while riding the wallet's own transport session). The early "VTA performs the password login and the wallet injects the cookie jar" variant was removed in #121. ## Recent Development +- **Dogwood → Eucalyptus RC (2026-08-19 → 09-18, ~275 commits, #120–#255)** — the busiest month in the repo's life, in three distinct phases. + - **Towards `VTI-Dogwood` (08-19 → 08-31; pnm-core 0.4.0 → 0.5.0 → 0.6.0).** *Why:* VTI #1146 turned on the four checks a Trust Task specification declares for itself, and 93 of the 141 task types the wallet speaks require a `proof` — none had one, so every `vault/*`, `acl/*`, `vta/webvh/*` and `vtc/*` call was being refused. **#131 signs every outbound document at the channel** (`signOutboundTask` in `RestChannel`, `TspChannel`, `DidcommVtaTransport`; the ~116 `buildTrustTask` call sites are untouched) and makes the signer a required constructor input so an unsigned channel cannot compile. Alongside: the did:webvh DID lifecycle and contexts (#123), the Trust-Task endpoint contract (#125), Trust Tasks 0.16 with **full JSON-Schema validation of inbound consent payloads** via `@cfworker/json-schema` (#127/#128/#130 — a hand-written `typeof` check had been rendering an out-of-enum `sideEffects` value as the *safest* green severity), provisioning over any advertised transport (#133), executor-initiated requests received over TSP as well as DIDComm (#134), transport health reporting (#143), a real demo RP website (#140), Trust-Task login over any transport (#141), and the per-site persona login trilogy (#144–#146). Tagged 08-31 with no GitHub Release: Dogwood was a deliberately quiet, behind-the-scenes cut. + - **`VTI-Dogwood-R1` (08-31 → 09-01; 9 commits, #147–#156; versions unchanged).** *Why re-cut:* an operator running several VTAs found that the wallet had a single wallet-wide `mediatorDid` inbox, so **every agent but one had its consent requests silently lost** — a gated action that never got its human check. #148–#150 make the inbox *per agent* (`settings.inboxes: Record`), derived from each agent's DID document rather than a hardcoded relay, with sessions keyed on the (agent, relay) pair. Two more silent failures fell with it: the `task-consent/granted` notice was being parsed at a pre-spec envelope shape, so approving on a device never auto-published on the page (#153), and a consent-gated edit prompted the human a third time for a payload already approved twice (#154 — a single-use replay exemption matched on the VTA's own `payloadDigest`). Diff vs Dogwood: 21 files, +1369/−158, all extension/inbound. + - **After R1 (09-01 → 09-18; pnm-core 0.7.0 → 0.9.1, tsp-js 0.3.0).** Four threads: + 1. **Management console (#155/#157, 09-01–03).** Nine panes on the Trust Context tree; proof of presence (a WebAuthn assertion) in front of irreversible controls; `setup` now asks *two* questions — how far the wallet's authority reaches (`adminScope` on `provision/integration/0.3`, needs trust-tasks 0.17.4 / trust-tasks-rs 0.18.3) and which context keeps its settings (#187). pnm-core 0.7.0 replaced every hand-written wire type with generated ones (finding that `swapAcl` had read fields the agent stopped sending at VTI #857); 0.9.0 added ACL **capability narrowing** (VTI #1279); 0.9.1 republished after 0.9.0 shipped a partial `dist`. + 2. **Persona (#159–#221, 09-06–09).** The wallet's half (`/persona`): the disclosure consent screen a person sees before their identity leaves, a `release: stepUp` refusal returned as a value rather than thrown, and a page-task policy that refuses the whole `persona/` prefix. The console's half (`/admin`): the **identity map** — attributes, faces, contexts, reach drawn in two hues — then *worlds* (the parts of a life), sensitive values masked and fetched on demand via `includeSensitive`, the claim-type table read from the agent, cursor pagination read to the end, and a vocabulary rule ("attribute", "face", never "fact"). + 3. **Data rooms (#204–#231, 09-08–11).** A rooms pane over `/rooms`: create a room by *minting its DID first* (`vta/webvh/dids/create` with `template: "room"`, keeping `signingKeyId` beside it), mint a host DID from the same form, issue invitation / membership / authority credentials in the room's name, browse and read records through the member's own agent, repair a broken epoch chain, and advertise TSP at each minted DID's mediator (pairs with VTI's `vti-rooms` / `room-host`). + 4. **Security and wire (#232–#255, 09-11–18).** The cross-repo **SEC-4045** egress review: `did:webvh` resolution and every mediator / VTA / push-gateway / page-proxied URL is vetted by `vti-didcomm-js` 0.8's `net-guard` before it is dialled and no redirect is followed; the page-facing listener checks `event.origin`; armor input is bounded; demo-rp answers CORS from an allow-list; CI actions are SHA-pinned; refusals are proved at the socket, not at a spy. Then **the wallet verifies every reply's proof and binds it to the addressed agent** (#215), Trust Tasks ride inside the published `tsp/0.1` binding envelope instead of bare (#246), **TSP Rev 3** (#253) with the XRFI/XRFA relationship handshake Rev 3 gates application messages on, and the two `vti-didcomm-js` bumps that follow (0.10.0 long frames #254; 0.10.1 single-label host guard, SEC #15, #255). Also: full-surface DID minting with a log reader (#247), `realign-keys` from the console (#245), and Chrome-refused `modulepreload` links dropped (#248). - **May 2026 (~90 commits)** — scaffold, DIDComm stack, SIOPv2 login, `window.vtaWallet` provider, vault M1/M2, onboarding via ephemeral did:key → `acl/swap-key`, WebAuthn-PRF holder-secret encryption, multi-VTA, first npm publish under `@openvtc`. - **June** — Trust Tasks 0.2 migration, pnm-core 0.2.0, Web Push wake-up (registering with the `vti-push-gateway`), camelCase wire, bearer caching / 401 retry. - **July** — transport-agnostic trust tasks + `vti-tsp-js` (#75–#81); generic `requestTask` relay (#84); approver identity with a PRF-gated signing key and a single-browser biometric approver "Phase 2" (#90/#91); co-located approver relay (#92/#93); the **D8 remediation** batch from the cross-repo security review — bounded fetches (R1.2), approver-inbox backoff (R1.5), durably record inbound *before* mediator ack (R1.6, #101), parse error bodies before throwing (R3.7); Vite 8 / TS 7. -- **August** — consent robustness (#104–#113: step-up on a verified reason, approval legs checked against the enrolled-executor set, durable pending approvals, "consent window failure = denial"); pnm-core 0.3.0 → 0.4.0 (#114, #118); tsp-js 0.2.0 pure-TS HPKE (#116/#117); Web Store packaging, per-site permissions, wallet UI (#119); browser-readable agent-name resolution (#120); **standalone layered library + cookies removal (#121)**; R1.6 documented as held via vti-didcomm-js 0.6.2's hand-off-then-ack (#122, 2026-08-19). -- **Direction**: Chrome Web Store submission; extracting pnm-core to its own repo; migrating `vault/` onto generated types. +- **August (to Cypress)** — consent robustness (#104–#113); pnm-core 0.3.0 → 0.4.0 (#114, #118); tsp-js 0.2.0 pure-TS HPKE (#116/#117); Web Store packaging, per-site permissions, wallet UI (#119); browser-readable agent-name resolution (#120); **standalone layered library + cookies removal (#121)**; R1.6 documented as held (#122). +- **Direction**: `VTI-Eucalyptus-RC-0` (2026-09-17) is in flight and not yet tagged here. Still open: Chrome Web Store submission (unchanged since May), extracting pnm-core to its own repo, an `allowHosts` pin list so a public name resolving to a private address is also refused, deleting the Rev 2 TSP codec once the last Rev 2 peer is gone, and a room that can answer as its own DID over a mediator. -See also: [[verifiable-trust-agent]], [[vti-didcomm-js]], [[rp-sdk-js]], [[affinidi-webvh-service]], [[didcomm]], [[trust-spanning-protocol]] +See also: [[verifiable-trust-agent]], [[vti-didcomm-js]], [[rp-sdk-js]], [[affinidi-webvh-service]], [[didcomm]], [[trust-spanning-protocol]], [[vta-topology]], [[coordinated-releases]] diff --git a/content/entities/vtafarm-api.md b/content/entities/vtafarm-api.md new file mode 100644 index 0000000..f3df0dd --- /dev/null +++ b/content/entities/vtafarm-api.md @@ -0,0 +1,77 @@ +--- +title: "vtafarm-api — VTA Farm provisioning backend" +type: entity +tags: [vta-farm, hosting, kubernetes, vault, go, provisioning, secondary, deployment] +date-updated: 2026-09-18 +repo: https://github.com/ic3software/vtafarm-api +--- + +# vtafarm-api — VTA Farm provisioning backend + +*Repo: [github.com/ic3software/vtafarm-api](https://github.com/ic3software/vtafarm-api)* + +vtafarm-api is the engine behind **VTA Farm**: a Go REST service that turns "create agent" in the [[vtafarm]] portal into a running [[verifiable-trust-agent|VTA]] — DNS, TLS, a Kubernetes namespace, a Vault-sealed seed, a published [[did-webvh]] document, a mediator wiring, and a super-admin handed to the user's own PNM. It is the oldest of the three Farm repos (first commit 2026-05-26, 97 commits) and the one where the security model actually lives. It describes itself as "managing VTA setup sessions with per-user namespace isolation", which is exactly right: a *session* is one provisioned stack, and everything about it is scoped to the user who owns it. + +## Why it is shaped this way + +Hosting someone else's trust agent is a delicate promise: the VTA is a signing oracle whose value is that *only its controller* can make it act. The API answers that in three layers. **Per-user namespaces** (`vtafarm-user-`) with their own ServiceAccounts and RBAC keep tenants apart. **HashiCorp Vault** holds each VTA's BIP-39 master seed at `secret/vta/user-/session-/master-seed`, readable only by a Vault role bound to that namespace's `vta` ServiceAccount; the API's own AppRole policy *deliberately has no read capability on any seed path* — it provisions and tears down access but never reads a secret. **Control stays with the user**: the Farm only ever runs `vta import-did --role admin` with a DID minted by the user's local `pnm setup`, so the admin key never exists on Farm infrastructure. What the operator *can* do is documented honestly: the ClusterRole includes `pods/exec` and PVC access, config exports reveal mediator admin material, and whoever holds Vault's root token holds everything — this is Vault-sealed hosting, not a TEE. + +## What It Does + +**Provisioning a VTA Only session** (the path every individual takes): + +1. `POST /setup` validates, creates a proxied Cloudflare A record `vta-.firstperson.dev`, persists the session. +2. `EnsureUserEnvironment` (namespace, `pod-operator` and `vta` ServiceAccounts, Role, RoleBinding) and `EnsureUserAccess` (Vault policy + kubernetes-auth role `vta-user-`). +3. Renders `vta-setup.toml`: REST, [[didcomm|DIDComm]] **and** [[trust-spanning-protocol|TSP]] enabled (#25); `[secrets] backend = "vault"`; `[messaging] kind = "existing"` pointing at the platform stack's mediator DID; `[vta_did] kind = "create_webvh"` at the platform DID host under `/-vta`; CORS pre-allowing the [[vta-browser-plugin|VTA Wallet]] extension origin. +4. Runs `vta setup` as a one-off Job on a 200 Mi PVC; parses the VTA DID and DID log from stdout; publishes the `did.jsonl` to the [[affinidi-webvh-service|did-hosting daemon]] using the Farm's own `did:key`, enrolled in that daemon's ACL as admin. +5. Parks at `awaiting_admin_did` until the user supplies the `did:key` from `pnm setup`; then a provision Job runs `vta import-did --role admin` (no context restriction — a super-admin) and `did-mgmt servers add`. +6. Creates Deployment, Service and a Traefik Ingress (wildcard TLS from the controller's default TLSStore, no per-Ingress config) and marks the session `running` only when `/health` reports Ready (0.4.0). + +Teardown reverses all of it — DNS, hosted DID and ACL entry, Kubernetes resources, the Vault seed. + +**Full Stack** (behind `beta_access`) runs the same pipeline for four components — VTA, mediator ([[affinidi-tdk]]), DID-hosting daemon and VTC ([[verifiable-trust-infrastructure]]) — on four hosts, every one using Vault kubernetes auth, the mediator keeping messages in fjall on its PVC (no Valkey), the VTC image required to be built with `--features vault-secrets`. It is the Kubernetes automation of [[vti-setup]]'s single-host explore flow, including the offline sealed-bundle steps. + +**The platform stack** is one Full Stack at `vta.` / `mediator.` / `dids.` / `vtc.firstperson.dev`, owned by a system account, that every VTA Only agent depends on; `vta_only` is refused (503, with a reason) until it is running. Admins can add co-admins to its VTA from the panel (#23) — which scales the VTA to zero, runs an ACL Job against its fjall store and scales it back. + +Around the core: **domains** (`managed` / `platform` / `custom` — custom zones verified by TXT + four CNAMEs, certificates via cert-manager HTTP-01); **stack sharing** (a share code lets up to ten VTA Only agents attach to a Full Stack's mediator and DID host); **capacity gating** (#14); **monitor endpoints** for UptimeRobot (#13); per-session and batch **image upgrades**; config/log **exports** (#34); and **load tests** (#37, #39). Images are listed live from GHCR (`ghcr.io/ic3software/{vta,mediator,did-hosting-daemon,vtc}`), newest first with `latest` flagged — the Farm does **not** pin a [[coordinated-releases|coordinated release]]; each session records the tag it was created with. + +## Components + +| Package | Role | +|---------|------| +| `internal/setup/` | orchestrator state machines (`vta_only`, `full_stack`, VTC), TOML templates, stdout parsers; resumes interrupted sessions on restart | +| `internal/k8s/` | client-go: namespaces, Jobs, Deployments, PVCs, Ingress/Traefik middleware, cert-manager Certificates, readiness, exec | +| `internal/vault/` | per-user policy and kubernetes-auth role; seed deletion | +| `internal/didhosting/` | control-plane client per daemon URL, cached tokens (0.4.0) | +| `internal/cloudflare/`, `internal/dnscheck/` | proxied A records; custom-domain verification | +| `internal/handler/` | user, admin, setup, domain, sharing, upgrade, monitor, load-test, signup, passkey routes (OpenAPI at `/docs`) | +| `migrations/` | 28 golang-migrate steps (admins → users → sessions → passkeys → full-stack → domains → sharing → grants → load tests) | +| `helm/vtafarm-api/` | chart with bundled PostgreSQL 18.4, ClusterRole, Vault/GHCR/WebAuthn values | +| `docs/` | nine design documents — the authoritative account of each feature | + +Stack: Go 1.26, Gin, GORM, PostgreSQL 18, client-go v0.36, passkeys via WebAuthn, HS256 JWT cookies. + +## Dependencies & relationships + +Consumed by [[vtafarm]]; deployed and given its Vault by [[vtafarm-k8s]] (stack 04 installs Vault, `vault-bootstrap.sh farm` mints the API's AppRole; stack 05 installs the chart). Drives the `vta`, `mediator`, `did-hosting-daemon` and `vtc` binaries by their `setup --from` recipes exactly as [[vti-setup]] documents for a human. The user's side of the handshake is the `pnm` CLI from [[verifiable-trust-infrastructure]]. + +## Recent Development + +Releases: **v0.1.0**, **v0.2.0** (2026-08-19), **v0.3.0** (08-20), **v0.3.1** (08-30), **v0.4.0** (08-31). + +- **09-13** SIOPv2 wallet-login design, proposed (#40); **09-12** ephemeral load-test admin DID (#39); **09-04** Apache-2.0 + DCO (#38). +- **08-31 v0.4.0** load tests (#37); readiness-gated `running` (#36). **08-30 v0.3.1** full-stack mediators `cors = "any"` (#35). **08-20 v0.3.0** exports (#34); PVCs at `/work/` (#33). +- **08-19 v0.1.0/v0.2.0** GHCR publishing, changelog; **Vault charts handed to vtafarm-k8s**; retaining StorageClass; origin and zone from config (#31, #32). +- **08-17/18** ingress-nginx → **Traefik** (#27, #28); VTA Wallet origin allowed; monitor window (#30). +- **08-12 TSP advertised across all four components (#25).** **08-02/03** VTA Only on a shared custom stack (#22); platform-stack co-admins (#23). +- **07-26 → 07-31** custom domains (#17, #18); `full_stack_with_vtc` folded (#16); admin delete (#15); env-pasted infra values removed (#19); shared dev PostgreSQL (#20). +- **07-09 → 07-23** admin session list and batch upgrades; signup requests (#9); self-service upgrades (#10); resource tuning (#11); dashboard (#12); monitor (#13); capacity gate (#14). +- **07-01 → 07-08** Full Stack (#5); Vault in mediator and DID hosting (#6); VTC (#7); custom naming (#8). +- **06-09 → 06-26** VTA Only wizard (#1); passkey-only auth (#2); rename (#3); **master seed moved from a K8s Secret into Vault (#4, 06-22)**; VTA linked to the DID-hosting control plane; single provision Job. +- **05-26/27** scaffold: per-user-namespace pod management, Helm deploy, Postgres 18. + +### Maturity and known gaps + +Production for individuals since June; Full Stack, sharing and custom domains are beta. Open by design: how a *user-supplied* DID host would authorise the Farm; single-region Cloudflare-only DNS; no per-connection revocation on shared stacks; SIOP login unbuilt. + +See also: [[vtafarm]], [[vtafarm-k8s]], [[vti-setup]], [[vta-topology]], [[verifiable-trust-agent]], [[affinidi-tdk]], [[affinidi-webvh-service]] diff --git a/content/entities/vtafarm-k8s.md b/content/entities/vtafarm-k8s.md new file mode 100644 index 0000000..d183609 --- /dev/null +++ b/content/entities/vtafarm-k8s.md @@ -0,0 +1,69 @@ +--- +title: "vtafarm-k8s — VTA Farm cluster (OpenTofu on Hetzner)" +type: entity +tags: [vta-farm, hosting, kubernetes, vault, opentofu, hetzner, infrastructure, secondary, deployment] +date-updated: 2026-09-18 +repo: https://github.com/ic3software/vtafarm-k8s +--- + +# vtafarm-k8s — VTA Farm cluster (OpenTofu on Hetzner) + +*Repo: [github.com/ic3software/vtafarm-k8s](https://github.com/ic3software/vtafarm-k8s)* + +vtafarm-k8s is the ground the Farm stands on: OpenTofu stacks, a Makefile and thirteen runbooks that take an empty Hetzner Cloud project to a running [[vtafarm]] + [[vtafarm-api]], with HashiCorp Vault underneath. It is the **sysop deploy stream** that [[vti-setup]] retired its systemd docs in favour of in the Cypress pass and labelled "coming soon" — Kubernetes, Vault as the secret store, TLS from cert-manager — now real, and generic enough that anyone with a Hetzner token and a Cloudflare zone can run their own Farm. In the [[vta-topology]] vocabulary, this is how you become a Verifiable Trust Service Provider. + +## Why this shape + +The README states the premise in one line: *"Vault keeps the master seed of every VTA encrypted and separated per user, which is what makes a farm safe enough to run vtafarm on."* A [[verifiable-trust-agent|VTA]]'s seed is the root of every key the user will ever have ([[bip32-key-derivation]]), so a multi-tenant host has to answer where that seed rests and who can reach it. The answer here is a three-peer Raft **farm Vault** with one policy and one Kubernetes-auth role per user namespace, seeds at `secret/vta/user-/session-/master-seed`, and each tenant pod authenticating with its own ServiceAccount JWT bound to its own namespace. The API's bootstrap policy can create and delete tenant access but has *no read capability on any seed*. + +A Vault cannot unseal itself after a restart, and a farm whose pods block on a human every reboot is not highly available. So a second, single-pod **transit Vault** holds one `autounseal` key: it is initialised with Shamir keys (5 shares, threshold 3) and unsealed by hand once; the farm Vault unseals against it thereafter. The runbook is candid that an in-cluster transit Vault protects data at rest and removes the manual step but does not defend against full compromise of a running cluster — the token that unwraps the farm's root key lives in that cluster. Moving to a cloud KMS is a one-stanza change. Initialisation and unsealing are deliberately *not* in OpenTofu so recovery keys and root tokens never land in state. + +## What It Does + +Five stacks, applied in order; each waits on the one before: + +| Stack | Builds | Once per | +|-------|--------|----------| +| `01-infra` | 3-node HA **k3s** on Hetzner (`cx23`, nbg1, spread placement, private network, load balancer, etcd snapshots to Object Storage every 6 h) | organisation | +| `02-rancher` | **Rancher** on that cluster, with cert-manager and Let's Encrypt | organisation | +| `03-rke2-clusters/` | an **RKE2** cluster Rancher creates for a farm (`cx33` servers, canal CNI, Traefik ingress, hcloud CCM/CSI) — or a single-node **dev** cluster (#21) | farm | +| `04-vtafarm-platform/` | **cert-manager**, **Longhorn** storage (one replica per volume, daily S3 backups) and the **two Vaults** | farm | +| `05-vtafarm-app/` | the **vtafarm** and **vtafarm-api** Helm charts from GHCR, the wildcard TLS chart, generated JWT and DB secrets | farm | + +All state lives in a locked S3 backend on Hetzner Object Storage (#15), one state file per cluster directory; `terraform.tfvars` files are synced through the same bucket rather than committed. The Makefile fronts everything (`make new-rke2-cluster CLUSTER=…`, `make apply-vtafarm-platform …`, `make vault-bootstrap … TARGET=farm|transit`, `make kubeconfig-merge-rke2 …`). The whole management layer costs about **€38/month**; a farm cluster adds its own nodes. + +The thirteen runbooks are the sysop's manual the wiki was waiting for: `vault.md` (init, unseal, the isolation model, day-2), `backup-restore.md` (four failure scenarios and a drill, #19), `cluster-migration.md` (moving a live farm — database, Vault and tenants — to a new cluster, #18), upgrades for every layer, operations, testing (the HA failover test), troubleshooting, teardown, cost, remote state, an OpenTofu primer, and `design-decisions.md`, which explains every pin. + +## Components + +| Path | Role | +|------|------| +| `stacks/01-infra`, `02-rancher` | management layer, built once | +| `stacks/03…05/_template` | per-cluster scaffolds instantiated by `scripts/new-*.sh` | +| `modules/rke2-custom-cluster` | Rancher-managed RKE2 on Hetzner, rolling upgrades with concurrency 1, drain config (#20), stable machine plans (#23) | +| `modules/vtafarm-platform` | cert-manager, Longhorn + backup chart, farm Vault and transit Vault (with an in-cluster PKI chart) | +| `modules/vtafarm-app` | the two application Helm releases and the TLS chart | +| `scripts/` | `vault-bootstrap.sh` (took over the API's bootstrap), etcd snapshot/restore, OS and package upgrades, cluster migration, kubeconfig helpers | + +**Pinned versions** (`design-decisions.md` explains each): k3s **v1.35.7** and Rancher **2.14.3** (the newest k3s Rancher's chart accepts); RKE2 **v1.35.7+rke2r1**; cert-manager **v1.21.1**; Longhorn **1.12.1**; Vault Helm chart **0.33.0**; hcloud CCM 1.34.0 / CSI 2.22.1; OpenTofu ≥ 1.12; Ubuntu 24.04. Application versions are the two chart versions in stack 05's tfvars (`vtafarm_version`, `vtafarm_api_version`), each chart's `appVersion` being its image tag. + +## Dependencies & relationships + +Produces the cluster, Vault and `vtafarm-api-vault` Secret that [[vtafarm-api]] requires before it will start, and installs [[vtafarm]] beside it. The VTA, mediator, DID-hosting and VTC images those provision come from GHCR and are chosen per session, not here. Hetzner Cloud and Cloudflare are hard dependencies today. Nothing in this repo is specific to the project's own `firstperson.dev` farm; the domain and credentials are inputs. + +## Recent Development + +No tagged releases — it is infrastructure, applied from `main`. 32 commits since 2026-08-10, almost all in the three weeks around the first published Farm releases. + +- **09-11** avoid RKE2 machine-plan churn (#23). **09-04** Apache-2.0 + DCO (#22). +- **08-30** single-node RKE2 **dev mode** (#21); drain configured (#20). +- **08-22 → 08-27** locked S3 state backend (#15); helm provider 3.x (#16); Longhorn S3 backups (#17); **live-farm migration** (#18); backup/restore drills (#19). +- **08-19 → 08-21** **stack 05 installs the applications**; **Vault migrated in from vtafarm-api (#11)** with its upgrade runbook; Rancher owns Traefik; Longhorn at one replica; README trimmed (#13). +- **08-12 → 08-16** Object Storage backups and Ubuntu upgrades (#1); Rancher-managed RKE2 hosts (#2); CLAUDE.md (#6); Terraform → **OpenTofu** (#7); rolling package upgrades (#8). +- **08-10/11** first commit; a 3-node k3s HA cluster running Rancher on Hetzner (nbg1, `cx23`); bootstrap, CCM and certificate-order fixes. + +### Maturity and known gaps + +Young but already exercised by migrations and drills. Single region, Hetzner-only, Cloudflare-only DNS; Longhorn at one replica trades durability for cost (S3 backups are the safety net); the transit-Vault seal is explicitly interim pending a cloud KMS; and the Farm is not a TEE — for hardware-isolated keys the VTA's Nitro mode remains a separate, self-hosted path. + +See also: [[vtafarm]], [[vtafarm-api]], [[vti-setup]], [[vta-topology]], [[verifiable-trust-agent]], [[verifiable-trust-infrastructure]] diff --git a/content/entities/vtafarm.md b/content/entities/vtafarm.md new file mode 100644 index 0000000..ce8648d --- /dev/null +++ b/content/entities/vtafarm.md @@ -0,0 +1,74 @@ +--- +title: "vtafarm — VTA Farm portal (frontend)" +type: entity +tags: [vta-farm, hosting, kubernetes, frontend, react, passkeys, secondary, deployment] +date-updated: 2026-09-18 +repo: https://github.com/ic3software/vtafarm +--- + +# vtafarm — VTA Farm portal (frontend) + +*Repo: [github.com/ic3software/vtafarm](https://github.com/ic3software/vtafarm)* + +**VTA Farm** is the hosted service where a person gets a [[verifiable-trust-agent|Personal VTA]] without running any infrastructure — and this repo is the part of it you can see. For months the wiki described the Farm only through its users ([[vti-setup]]'s "Path A", the [[vta-topology]] table) and promised that the sysop *deploy* stream — Kubernetes, HashiCorp Vault, the Farm itself — was "coming soon". In August 2026 it shipped as three open-source repos under the `ic3software` org: this portal, the [[vtafarm-api]] that does the provisioning, and [[vtafarm-k8s]], which builds the cluster it all runs on. Together they are the reference **Verifiable Trust Service Provider** the spec vocabulary anticipated. + +## Why a portal + +A cloud VTA has to be always-on, reachable at a public HTTPS name, hold a [[did-webvh]] log somewhere resolvable, and be wired to a mediator — a weekend for a sysop, a wall for everyone else. The portal turns it into a form: sign in with a passkey, name your agent, pick an image, click **Create**. What makes it *acceptable* to host other people's agents — the seed sealed in Vault, one namespace per user, an operator that provisions access but never reads a seed — is the API's and the cluster's job. The portal's job is to make it legible: what is running, what its DIDs are, and how to hand control to your own PNM. + +## What It Does + +A React 19 + Vite 8 + TypeScript 6 single-page app (Tailwind v4, shadcn/ui, Radix, Lucide; `@simplewebauthn/browser` for passkeys), served by nginx from a container that renders the API URL into `/config.js` at start-up so one image serves every deployment (#35). Five routes: + +- **`/`** — the marketing site (hero, how-it-works, features). +- **`/login`, `/register`, `/recover`** — passkey-only user auth (#1). Accounts come from an invitation link, or since July from an email **signup request** (#6) — the "open self-signup" the wiki has recorded since then. +- **`/portal`** — the user portal: *Agents* list, *Create agent*, per-agent detail, *Domains*, *Settings*. +- **`/admin/login`, `/admin/enroll`** — admin auth via single-use enrolment tokens, then passkeys. +- **`/admin`** — the operator console. + +### Creating an agent + +The create form offers two modes. **VTA Only** deploys just the VTA, pointed at the Farm's shared *platform stack* mediator and DID host; **Full Stack** (gated by an admin-set `beta_access` flag) deploys VTA + mediator + DID-hosting daemon + VTC in your own namespace. Images are chosen from a live list of GHCR tags with the newest marked *latest* and preselected. A phase stepper follows the API's state machine with server-sent log streaming. The one human step: after `vta setup` runs, the page shows your **VTA DID** and waits for an **Admin DID** — you run `pnm setup` locally ("connect to an existing non-TEE VTA"), paste its temporary `did:key` back, click **Provision agent**, and the Farm imports it as your VTA's super-admin. Nobody at the Farm ever holds that key. From there `pnm health`, the [[openvtc|OpenVTC TUI]] and the [[vta-browser-plugin|browser wallet]] (its extension origin is pre-allowed in CORS) connect as they would to any VTA. + +### Living with an agent + +The session page shows endpoints and DIDs, a **self-service version card** to move any component to another registry tag (#7), a **Configs & logs** export (#37) — live zips of rendered `config.toml`s or pod logs, explicitly a credential disclosure — and a type-the-name delete. A Full Stack session gains a **Share** panel (#27): mint a share code, and a friend can create a VTA Only agent that uses *your* mediator and DID host ("Customize" in the create form). The **Domains** page (#18, #21) verifies a zone you own (one TXT plus four CNAMEs) so a Full Stack can run at `vta.yourdomain` with a Let's Encrypt certificate. + +### The admin console + +Dashboard with placement-simulated cluster headroom (#8); Sessions with pagination, mode filter and batch image upgrades; Users (flip `beta_access`), Invitations, Signup requests, Admins, Security, Audit; **Platform stack** creation and co-admin grants (#30); and since v0.4.0 **Load testing** — 1–50 VTA Only sessions with an ephemeral admin DID, torn down in one action (#38, #41). + +## Components + +| Path | Role | +|------|------| +| `src/pages/portal/` | Agents, CreateVTA, SessionDetail, FullStackOutputs, Domains, Settings, version/export cards | +| `src/pages/admin/` | Dashboard, Sessions, Users, Invitations, Admins, Security, Audit, PlatformStack, LoadTesting | +| `src/contexts/` | separate user and admin auth contexts (cookie-scoped to `/admin`) | +| `helm/vtafarm/` | chart; `Chart.yaml` is the single version number, `appVersion` = image tag (#36) | +| `docker/` | entrypoint that renders `$API_URL` into `/config.js` | +| `docs/` | frontend design notes for custom domains and stack sharing; the release guide | +| `claude-design/` | the HTML mockups the UI is built from (violet primary, IBM Plex) | + +## Dependencies & relationships + +Everything the portal shows comes from [[vtafarm-api]] (`/api/v1/...`); it is deployed by [[vtafarm-k8s]] stack 05 next to the API, behind Traefik. It is the "VTA Farm" of [[vti-setup]]'s Path A and the cloud-personal-VTA row of [[vta-topology]]. It is *not* a VTA client: after provisioning, your PNM surfaces talk to the VTA directly. + +## Recent Development + +Releases: **v0.1.0** and **v0.2.0** (2026-08-19), **v0.3.0** (08-20), **v0.4.0** (08-31) — image `ghcr.io/ic3software/vtafarm`, chart `ghcr.io/ic3software/charts/vtafarm`, signed tags. 79 commits since 2026-06-08. + +- **09-12** ephemeral load-test admin DID (#41); **09-04** Apache-2.0 + DCO (#39). +- **08-31 v0.4.0** load-test UI (#38). **08-20 v0.3.0** configs & logs download (#37). +- **08-19 v0.1.0 / v0.2.0** first published release — GHCR images and charts, changelog, release guide; runtime API URL (#35); chart appVersion as image tag (#36); `imagePullSecrets` dropped (breaking). **08-17** Traefik (#33). +- **08-02/03** VTA Only against a shared custom stack (#27); platform-stack admin grant (#30). +- **07-26 → 07-28** custom domains (#18, #21); `full_stack_with_vtc` folded into `full_stack` (#16); admin delete (#15); env-derived values removed (#23); fixes (#25, #26). +- **07-09 → 07-23** admin Sessions view and batch upgrades; **signup requests** (#6, open self-signup); version card (#7); dashboard (#8); "Unavailable" when full (#13). +- **07-01 → 07-08** Full Stack mode (#4); default names `myvta`/`myvtc` (#5). +- **06-08 → 06-26** scaffold from Claude-generated mockups; portal/admin split, log streaming, Docker/Helm/GHA; invitation registration; **passkey-only auth** (#1) and rename to `vtafarm` (#2); admin-DID validation (#3). + +### Maturity and known gaps + +0.x, values may change between releases. Full Stack and stack sharing are behind `beta_access`; custom domains ship but wait on cluster prerequisites; the marketing copy still promises "hardware enclave" keys and "pick a region", which is not what the current Vault-backed, single-region deployment does. A **VTA Wallet SIOPv2 login** to the portal is designed but not built (API #40). + +See also: [[vtafarm-api]], [[vtafarm-k8s]], [[vti-setup]], [[vta-topology]], [[verifiable-trust-agent]], [[openvtc]], [[vta-browser-plugin]] diff --git a/content/entities/vti-didcomm-js.md b/content/entities/vti-didcomm-js.md index 0bb1b0f..34810f8 100644 --- a/content/entities/vti-didcomm-js.md +++ b/content/entities/vti-didcomm-js.md @@ -1,8 +1,8 @@ --- title: "vti-didcomm-js — browser-side DIDComm v2" type: entity -tags: [didcomm, javascript, browser, library, secondary] -date-updated: 2026-08-19 +tags: [didcomm, javascript, browser, library, tsp, security, secondary] +date-updated: 2026-09-18 repo: https://github.com/OpenVTC/vti-didcomm-js --- @@ -10,22 +10,36 @@ repo: https://github.com/OpenVTC/vti-didcomm-js *Repo: [github.com/OpenVTC/vti-didcomm-js](https://github.com/OpenVTC/vti-didcomm-js)* -A focused, dependency-light, ESM-only implementation of **exactly the subset of [[didcomm|DIDComm v2]] that the VTI's browser flows need** — so the [[vta-browser-plugin|VTA Wallet]] can talk to a [[verifiable-trust-agent|VTA]] through a mediator without a Rust/WASM toolchain. It is byte-compatible on the wire with `affinidi-messaging-didcomm` (the crate the VTA and the [[affinidi-tdk|TDK]] mediator use), verified by a Rust round-trip helper in CI. Runs in browsers and Node 20+; crypto from WebCrypto + @noble; 150+ tests including RFC 7518 vectors. +A focused, dependency-light, ESM-only implementation of **exactly the subset of [[didcomm|DIDComm v2]] that the VTI's browser flows need** — so the [[vta-browser-plugin|VTA Wallet]] can talk to a [[verifiable-trust-agent|VTA]] through a mediator without a Rust/WASM toolchain. It is byte-compatible on the wire with `affinidi-messaging-didcomm` (the crate the VTA and the [[affinidi-tdk|TDK]] mediator use; the CI round-trip helper now pins 0.15, matching both). Runs in browsers and Node 20+; crypto from WebCrypto + @noble; 150+ tests including RFC 7518 vectors. Since September 2026 it is also where the wallet's **network egress policy** lives: because a browser extension has no DNS API and cannot inspect a redirect, "which hosts may this client ever dial?" has to be decided by literal before the fetch, and this library is the one place every mediator, VTA and did:webvh URL passes through. -It was built inside the [[verifiable-trust-infrastructure|VTI]] workspace (design note `docs/05-design-notes/didcomm-js-implementation.md`) and **extracted into its own repository on 2026-05-21**; its primary consumer is `@openvtc/pnm-core`, which swapped onto it the same day, replacing a WASM scaffold. The `pnm-relay` is the other named consumer. +It was built inside the [[verifiable-trust-infrastructure|VTI]] workspace (design note `docs/05-design-notes/didcomm-js-implementation.md`) and **extracted into its own repository on 2026-05-21**; its primary consumer is `@openvtc/pnm-core`, which swapped onto it the same day, replacing a WASM scaffold. (The `pnm-relay` once named as a second consumer no longer exists — it was consolidated into pnm-core in August.) ## The Subset - **Authcrypt** (ECDH-1PU + A256KW + A256CBC-HS512, sender-bound) and **anoncrypt** (ECDH-ES) over X25519 or P-256; `routing/2.0/forward` -- DID resolution: did:key (Ed25519 / X25519 / P-256 / secp256k1), did:peer numalgo 2, [[did-webvh|did:webvh]] (via `didwebvh-ts`, full hash-chain + Data Integrity verification), pluggable dispatcher -- VTA REST `/auth/` challenge-response + refresh; mediator transport (ATM challenge auth, WebSocket with subprotocol bearer, message-pickup 3.0 live delivery, `sendAndWait`); TSP frame multiplexing on the same socket (0.6.0) +- DID resolution: did:key (Ed25519 / X25519 / P-256 / secp256k1), did:peer numalgo 2, [[did-webvh|did:webvh]] (via `didwebvh-ts` 2.8, full hash-chain + Data Integrity verification — but since 0.9.0 the library fetches the log *itself* through the guard, so no host is contacted unvetted), pluggable dispatcher with a bounded LRU cache (500 entries; did:key / did:peer never cached, since they are free to mint and resolve offline) +- VTA REST `/auth/` challenge-response + refresh; mediator transport (ATM challenge auth, WebSocket with subprotocol bearer, message-pickup 3.0 live delivery, `sendAndWait`); [[trust-spanning-protocol|TSP]] frame multiplexing on the same socket — key-blind, acked after the consumer finishes, recognising both short (`-E`) and Rev 3 long (`--E`) framing (`./tsp-frame`) +- **`./net-guard`** (0.8.0): `assertSafeEndpoint`, `guardedFetch`, `BlockedEndpointError` (`code: "E_BLOCKED_ENDPOINT"`, typed `reason`) and a `netPolicy { allowInsecure, allowPrivate, allowHosts }` threaded through mediator resolution, auth, `MediatorSession`, VTA REST auth and did:webvh resolution; `./net-guard/node` adds a `dns.lookup` replacement for server-side consumers - *Not* implemented: multi-recipient JWE, XChaCha20, JWS-only, BBS+, other peer numalgos -## Recent Development +## Versions at the coordinated releases + +| Cypress | VTI-Dogwood | VTI-Dogwood-R1 | main (2026-09-18) | +|---------|-------------|----------------|-------------------| +| 0.6.2 | 0.7.0 | 0.7.0 (same commit) | 0.10.1 | -- **0.1.0 → 0.4.2 (2026-05-21 → 05-31)** — initial release; did:peer + P-256; `onMessage`; ack delivered messages with a TTL resolution cache; ack with `sha256(JWE)` and skip mediator frames (fixing an infinite ack/status loop); WebSocket close-code diagnostics. -- **0.5.0 (06-01)** — spec-correct ECDH-1PU `cc_tag` length prefix — a breaking authcrypt wire change with dual-KEK fallback, pairing with `affinidi-messaging-didcomm` ≥ 0.14. -- **0.6.0 (07-05)** — TSP frame multiplexing over the mediator socket (`onTspFrame`). **0.6.1 (07-16)** — REST auth realigned to canonical `trusttasks.org/spec/auth/*` types (D8-F2). **0.6.2 (07-16)** — hand inbound off *before* acking: at-least-once with dedup (D8-F3 / R1.6) — the change the browser plugin pins as its correctness floor. Docs/CLAUDE.md refresh 07-19. -- **Direction**: stable; serves as the pinned crypto floor for the plugin. +The browser plugin pins two floors as *correctness constraints*: `^0.6.2` (hand-off-then-ack) and, since #254, `^0.10.0` (long-framed TSP replies). + +## Recent Development -See also: [[vta-browser-plugin]], [[didcomm]], [[affinidi-tdk]], [[verifiable-trust-infrastructure]] +- **0.7.0 → 0.10.1 (2026-08-28 → 09-18; PRs #10–#25)** — from a stable floor to an actively hardened one. + - **0.7.0 (08-29, #11 — in `VTI-Dogwood`).** *Why:* the 0.6.0 TSP demux routed a `-E` frame to `onTspFrame` and returned before the code that acks, but the mediator stores TSP through the same delete-to-ack path as DIDComm — so **every TSP message a client ever received stayed queued and was redelivered on every reconnect, indefinitely**, masked by request/reply discarding the stragglers. Inbound TSP frames are now handed off, awaited, then acked (R1.6 for TSP), with a throwing consumer withholding the ack. Dogwood-RC-1 (#10) and Dogwood (#12) were dependency refreshes only; Dogwood and Dogwood-R1 are the same commit here. + - **0.8.0 (09-11, #13).** The **egress guard**. A mediator's REST, auth and WebSocket URLs come out of *its* DID document, and a VTA's base URL is written from one at onboarding — none of them chosen by the wallet — so a document naming `https://127.0.0.1` or `169.254.169.254` was a request from inside the user's network with the wallet's credentials attached. Every such endpoint is now vetted (no userinfo; no loopback / private / link-local / CGNAT / local-only names, including IPv4-mapped and 6to4 spellings), no auth request follows a redirect, response bodies no longer leak into error messages, and `allowInsecure` and `allowPrivate` become independent flags (local dev now needs both). Also realigned the Rust round-trip helper to `affinidi-messaging-didcomm` 0.15, which had been red in CI since the 0.5.0 `cc_tag` fix. + - **0.9.0 / 0.9.1 (09-12, #14/#15; the `sec-4045` review).** `did:webvh` resolution checks the host *before* fetching the log — `unpackInbound` resolves an inbound frame's `skid` before the frame is authenticated, so anyone who can route a frame through the mediator could otherwise make a client GET any host — and takes over the fetch from `didwebvh-ts`, whose plaintext downgrade fired on any identifier merely *containing* `localhost`. 0.9.1 bounds the resolver cache, which an attacker-supplied `skid` could previously grow without limit. Same day: CI actions SHA-pinned with a read-only token (#16), Dependabot with a 7-day cooldown, TypeScript 7.0.2, @noble/curves 2.4, didwebvh-ts 2.8 (#18–#22), and a regression-gate suite that drives the full SEC-4045 bypass vector set through the real `fetch` and socket paths (#23). + - **0.10.0 (09-15, #24).** TSP spec Rev 3 widened the `-E` count to cover the ciphertext, so any message past ~12 KB is long-framed and its text starts `--E`; the `startsWith("-E")` classifier sent those to the DIDComm unpacker, which threw *before* the ack — a poison frame redelivered forever while the TSP consumer never heard. `isTspFrameText` / `isTspFrameBytes` now match both; the mediator's `affinidi_tsp::is_tsp` and the wallet's `vti-tsp-js` were fixed in step. + - **0.10.1 (09-18, #25, SEC #15).** The net-guard refuses bare single-label hosts (`intranet`, `metadata`, decimal/hex IP spellings) — a blind-SSRF gap the wallet's own did:webvh guard had already closed but the library had not. +- **0.5.0 → 0.6.2 (06-01 → 07-16)** — spec-correct ECDH-1PU `cc_tag` length prefix (breaking authcrypt wire change, pairs with `affinidi-messaging-didcomm` ≥ 0.14); TSP frame multiplexing (0.6.0); REST auth realigned to canonical `trusttasks.org/spec/auth/*` types (0.6.1, D8-F2); hand inbound off *before* acking (0.6.2, D8-F3 / R1.6) — the plugin's first correctness floor. +- **0.1.0 → 0.4.2 (2026-05-21 → 05-31)** — initial release; did:peer + P-256; `onMessage`; ack with `sha256(JWE)` and skip mediator frames; WebSocket close-code diagnostics. +- **Direction**: the guard's remaining browser gap — a public name that resolves to a private address — can only be closed by `allowHosts` with a pinned list the wallet does not yet hold. `VTI-Eucalyptus-RC-0` (2026-09-17) is not yet tagged here. + +See also: [[vta-browser-plugin]], [[didcomm]], [[trust-spanning-protocol]], [[affinidi-tdk]], [[verifiable-trust-infrastructure]] diff --git a/content/entities/vti-setup.md b/content/entities/vti-setup.md index 47488dc..3ad288c 100644 --- a/content/entities/vti-setup.md +++ b/content/entities/vti-setup.md @@ -1,8 +1,8 @@ --- title: "vti-setup — VTI Setup Guides" type: entity -tags: [vti, setup, deployment, documentation, guides, secondary, cypress, kubernetes] -date-updated: 2026-08-19 +tags: [vti, setup, deployment, documentation, guides, secondary, cypress, dogwood, kubernetes, supply-chain] +date-updated: 2026-09-18 repo: https://github.com/OpenVTC/vti-setup --- @@ -10,9 +10,9 @@ repo: https://github.com/OpenVTC/vti-setup *Repo: [github.com/OpenVTC/vti-setup](https://github.com/OpenVTC/vti-setup)* -vti-setup is the operational companion to the ecosystem's code repos: tested, version-pinned walkthroughs (plus a curl-able bootstrap script) for standing up the full [[verifiable-trust-infrastructure|Verifiable Trust Infrastructure]] — the [[verifiable-trust-agent|VTA]], the mediator, DID hosting, and the VTC service — and for the things people do on top of it: joining communities and running one. +vti-setup is the operational companion to the ecosystem's code repos: tested, version-pinned walkthroughs (plus a bootstrap script) for standing up the full [[verifiable-trust-infrastructure|Verifiable Trust Infrastructure]] — the [[verifiable-trust-agent|VTA]], the mediator, DID hosting, and the VTC service — and for the things people do on top of it: joining communities and running one. -Where the code repos answer "what is this component?", vti-setup answers "how do I actually run this?" It's a documentation-only repo — guides and one bash bootstrap script — but every walkthrough carries a "Verified with" version matrix and a "Tested on" platform line, and the dominant maintenance theme is keeping the guides verified against the fast-moving upstream binaries. Since August 2026 the guides are pinned to the coordinated **`Cypress`** release ([[coordinated-releases]]): VTA 0.17.0 / Mediator 0.18.19 / DID Hosting Daemon 0.8.3 / VTC 0.11.58 / OpenVTC 0.3.1. +Where the code repos answer "what is this component?", vti-setup answers "how do I actually run this?" It's a documentation-first repo — guides, one bash bootstrap script and (since September 2026) a small release pipeline for that script — but every walkthrough carries a "Verified with" version matrix and a "Tested on" platform line, and the dominant maintenance theme is keeping the guides verified against the fast-moving upstream binaries. The sysop explore stream is pinned to the coordinated **`VTI-Dogwood`** release ([[coordinated-releases]]): VTA 0.23.2 / Mediator 0.20.2 / DID Hosting Daemon 0.8.3 / VTC 0.11.58. The developer guides still carry their `Cypress` matrices (VTA 0.17.0 / Mediator 0.18.19 / DHD 0.8.3 / OpenVTC 0.3.1) — they were not re-verified in the Dogwood pass. ## Organized by Persona, Not by Service @@ -23,8 +23,8 @@ The repo's core design choice: guides are grouped by **who you are**, each perso The individual-user path, rewritten for Cypress (#26): stand up a **Personal VTA** → install and bind the [[openvtc|OpenVTC TUI]] → join a community. The Personal VTA guide offers two paths: -- **Path A — VTA Farm** (vtafarm.firstperson.dev) — a managed VTA on a Kubernetes cluster run by the project; since July 2026 **open self-signup** (no invitation needed), passkey-based. The recommended default for individuals. The `pnm` binary (firstperson.dev downloads) checks your VTA, mediator, and DIDComm/TSP trust pings with `pnm health`. -- **Path B — self-hosted** — your own host, your own [[did-webvh|did:webvh]] log, your own mediator wiring ("the hard way") +- **Path A — VTA Farm** (vtafarm.firstperson.dev) — a managed VTA on a Kubernetes cluster run by the project; since July 2026 **open self-signup** (no invitation needed), passkey-based. The recommended default for individuals. The `pnm` binary (firstperson.dev downloads) checks your VTA, mediator, and DIDComm/TSP trust pings with `pnm health`. The Farm itself is now open source as [[vtafarm]] (the web front-end), [[vtafarm-api]] and [[vtafarm-k8s]] — this guide is the *user's* view of it. +- **Path B — self-hosted** — your own host, your own [[did-webvh|did:webvh]] log, your own mediator wiring ("the hard way"). Since #36 the guide says which PNM build you get: the sysop guide installs the keyring-free **`pnm-server`** build (secrets in plaintext config, right for a headless server); to drive that VTA from a workstation instead, install the default build, which keeps secrets in the OS keyring. Either way the binary is called `pnm`. The OpenVTC guide reflects the Cypress-era TUI: a clean four-section setup wizard (Get Started → Key Management → Profile Security → Setup Complete) that **mints no persona**; you authorise the TUI against your VTA with `pnm contexts create --id openvtc … --admin-expires 1h`, and it opens a [[trust-spanning-protocol|TSP]] or [[didcomm|DIDComm]] session — whichever the VTA advertises. The joining guide then has you mint a persona (M-DID) from the dashboard, enter the community's **DID or agent name** (`community.example.com/@acme`), optionally paste a [[invitation-credential|VIC]] or send an open request, and wait for an admin to approve in the VTC admin console (which creates the member + ACL row and issues the VMC + role VEC). The two-VRC peer-vouching flow described in the [[dtg-credential-spec|spec]] is explicitly labelled *target design, not current behaviour*. @@ -34,10 +34,10 @@ The VTC-operator path: bootstrap the community, author join/role policies, manag ### `sysop/` — running the infrastructure -Two streams, of which one is currently live: +Two streams, of which one is live: -- **`explore/`** — the learning stream. A throwaway single VM (`scripts/setup-explore.sh`: Ubuntu, Valkey, ufw, Rust, Node, Docker, nginx + certbot, four vhosts), everything as root, each component set up through its interactive wizard, with loud "no real keys here" warnings. Walks the full chain — `vta setup` (selecting REST, DIDComm **and TSP**, a seed-storage backend, the hardened encrypted store, automatic mediator ACL provisioning), mediator (TSP + DIDComm), DID Hosting Daemon (transport "Both DIDComm and TSP"; then registered with the VTA via `pnm did-mgmt servers add`, #24), VTC (transports TSP & DIDComm, trust-registry DID, `admin-ui` feature, #23; its DID published via the daemon under the WebVH path `vtc`) — threading cross-step values via "save this ID" tables. Source builds `git checkout Cypress` in VTI, the TDK and did-hosting-service (#33); pre-built binaries come from `download.firstperson.dev//latest/` (tracking the newest tagged release) or `/main/`. -- **`deploy/`** — the hardened production stream. **Coming soon**: the earlier systemd + TOML-recipe deploy docs were retired in the Cypress pass (#25) in favour of a planned **Kubernetes deployment** — HashiCorp Vault as the secret store, TLS via cert-manager/Let's Encrypt, TOML recipes — documented alongside the **VTA Farm**, so that deploying a VTA for an individual becomes straightforward (whether you run the cluster or use the Farm). Today the section is a placeholder that points at the explore stream. +- **`explore/`** — the learning stream. A throwaway single VM (`scripts/setup-explore.sh`: Ubuntu, Valkey, ufw, Rust, nginx + certbot, four vhosts), everything as root, each component set up through its interactive wizard, with loud "no real keys here" warnings. Walks the full chain — `vta setup` (selecting REST, DIDComm **and TSP**, a seed-storage backend, the hardened encrypted store, automatic mediator ACL provisioning), mediator (TSP + DIDComm), DID Hosting Daemon (transport "Both DIDComm and TSP"; then registered with the VTA via `pnm did-mgmt servers add`, #24), VTC (transports TSP & DIDComm, trust-registry DID, `admin-ui` feature, #23; its DID published via the daemon under the WebVH path `vtc`) — threading cross-step values via "save this ID" tables. Source builds `git checkout VTI-Dogwood` in VTI, the TDK and did-hosting-service (#35); pre-built binaries come from `download.firstperson.dev//latest/` (tracking the newest tagged release) or `/main/` (labelled *unverified*), with PNM taken from the `pnm-server/` path. **The setup script is no longer piped from `main` into bash**: since #37 you download it from a tagged GitHub Release, check it against a SHA-256 pinned in the guide (`VER=v1.0.0`), check `SHA256SUMS`, verify the build-provenance attestation with `gh attestation verify`, and only then `sudo bash setup-explore.sh `. +- **`deploy/`** — the hardened production stream. Still a placeholder: a one-paragraph README describing the intended shape — Kubernetes, TLS via cert-manager / Let's Encrypt, every secret in **HashiCorp Vault** "for full VTI stacks and standalone VTAs alike", TOML-recipe provisioning over the sealed-bundle bootstrap — followed by *"To be documented"* and a pointer back to explore. The earlier systemd + TOML-recipe deploy docs retired in the Cypress pass (#25) were preserved on 2026-09-15 under the tag **`archive/sysop-deploy`** ("Archive sysop/deploy before replacement with placeholder" — `sysop/deploy/01–04`, `setup-deploy.sh`, `bootstrap-user.sh` at the last commit that carried them). The Kubernetes deployment the placeholder promised has meanwhile shipped *outside this repo*, as [[vtafarm-k8s]] (with [[vtafarm-api]] and [[vtafarm]]); as of #37 nothing in vti-setup links to it yet, and the archive tag reads as the prelude to that replacement. Both streams use the **offline sealed-bundle bootstrap over DIDComm** — the same HPKE-sealed flow used when the VTA is air-gapped — even when all services share a host. @@ -45,18 +45,28 @@ Both streams use the **offline sealed-bundle bootstrap over DIDComm** — the sa | Component | Upstream | |-----------|----------| -| VTA (key store: BIP-39 seed, DIDs, contexts, ACL) + `pnm` client | [[verifiable-trust-infrastructure]] | +| VTA (key store: BIP-39 seed, DIDs, contexts, ACL) + `pnm` client (`pnm-server` build on the explore host) | [[verifiable-trust-infrastructure]] | | DID Host (`dids.` subdomain) | [[affinidi-webvh-service|did-hosting-service]] | | Mediator (DIDComm v2 + TSP) + `mediator-setup` | [[affinidi-tdk]] | | VTC service (community policy layer, admin UI) | [[verifiable-trust-infrastructure]] | | OpenVTC TUI | [[openvtc]] | +| Managed Personal VTA (Path A) | [[vtafarm]] / [[vtafarm-api]] / [[vtafarm-k8s]] | | Valkey | mediator storage backend (loopback-only, AOF persistence) | | nginx + certbot, ufw | TLS on four subdomains (`vta`, `mediator`, `vtc`, `dids`), firewall | -| Pre-built binaries | `download.firstperson.dev` (`/latest/` = newest tagged release, `/main/` = main-branch builds) | +| Pre-built binaries | `download.firstperson.dev` (`/latest/` = newest tagged release, `/main/` = main-branch builds) — **no checksums or signatures published yet**; the guide now says so and confines them to throwaway hosts | +| `setup-explore.sh` | a tagged GitHub Release of this repo, SHA-256-pinned in the guide and carrying a Sigstore build-provenance attestation | ## Recent Development -The repo is young (~33 PRs since the initial scaffold on 2026-04-28) and has been restructured three times — a sign the team is converging on how to *teach* the stack, not just build it. +The repo is young (~37 PRs since the initial scaffold on 2026-04-28) and has been restructured three times — a sign the team is converging on how to *teach* the stack, not just build it. The September theme is new: the setup docs are now held to the same **supply-chain standard** as the code they install. + +### Dogwood pin + trust the script you run as root — August–September 2026 (#35–#37, `archive/sysop-deploy`) + +- **Explore stream re-pointed at `VTI-Dogwood`** (#35, 08-30): the three source checkouts (VTI, affinidi-tdk-rs, affinidi-webvh-service) move from `Cypress` to `VTI-Dogwood`, and the walkthrough's matrix becomes VTA **0.23.2** / Mediator **0.20.2** / DHD 0.8.3 / VTC 0.11.58 — the RC-1 tag people were checking out had been superseded and no longer matched the walkthrough's prompts. Dogwood was a tag-only, "silent" release, so this is the one place its version set is written down for operators. +- **Non-interactive, keyring-free** (#36, 08-30, closes #34): piped into bash, the script had no terminal, so an `apt upgrade` that pulled in `keyboard-configuration` opened a debconf dialog nobody could answer. Every apt call now goes through an `apt_get()` helper that sets `DEBIAN_FRONTEND=noninteractive` / `NEEDRESTART_MODE=a` *on the command line* (sudo's `env_reset` strips exported variables). And because mainline PNM now keeps its secrets in the OS keyring — which a headless VPS lacks — the explore host installs the **`pnm-server`** build (`download.firstperson.dev/pnm-server/`), with the from-source build gaining `--features "config-session,tsp"` to match how that binary is built (without `tsp`, `pnm health` has no TSP round-trip probe). Each download carries a "do not correct this back to `pnm/`" comment. +- **Verify everything the script runs and downloads** (#37, Glenn Gore, 09-12): the script had installed Rust by piping `sh.rustup.rs` into `sh`, Node from NodeSource via `sudo -E bash`, and Docker's apt key with no fingerprint check — all as root, trusting whatever came over TLS. Now: the whole script is one `main()` called on its last line (a truncated download runs nothing) under `set -euo pipefail`; domain and email are validated before anything runs (they end up in nginx `server_name` and certbot arguments); Rust comes from Ubuntu's signed `rustup` package, with a SHA-256-pinned `rustup-init 1.29.1` fallback; Node.js is installed only at the optional DID Hosting UI build step; **Docker is gone** (nothing used it, and docker-group membership is root). The repo gained its first CI — `lint.yml` (shellcheck + a guard that fails on `curl | sh` patterns and on raw fetches from `main`), `release.yml` (on a `v*` tag: attach `setup-explore.sh` + `SHA256SUMS` to a GitHub Release with a build-provenance attestation, in two jobs so publishing runs in a protected `release` environment), Dependabot with a 7-day cooldown, and `RELEASING.md` (pin the hash in the guide *first*, then tag — "never move a published tag"). The guide's Step 3 now downloads from the release, checks both hashes and the attestation, and runs `sudo bash setup-explore.sh`. It also states plainly that `download.firstperson.dev` publishes no checksums — acceptable only on a throwaway explore box. +- **`archive/sysop-deploy`** (09-15, tagged by vthwang): preserves the pre-Cypress systemd deploy stream at its last commit, explicitly "before replacement with placeholder" — see the `deploy/` note above and [[vtafarm-k8s]]. +- Loose ends: the guide pins `VER=v1.0.0` (its SHA-256 matches the script on `main`), but that tag/Release was not yet visible on the remote at the time of writing; the developer guides' version matrices still say Cypress; `sysop/deploy/README.md` does not yet link to vtafarm-k8s. ### Cypress pass — July–August 2026 (#23–#33) @@ -81,4 +91,4 @@ The repo is young (~33 PRs since the initial scaffold on 2026-04-28) and has bee - Initial scaffold: architecture diagrams + a 12-scenario matrix; scenario guides (S01 online, S05/S07 offline-VTA, S08), persona guide P01, tutorials T01/T02, component renames with compatibility rows. None of these files survive in the tree — they were absorbed by the two restructures. -See also: [[verifiable-trust-infrastructure]], [[verifiable-trust-agent]], [[openvtc]], [[affinidi-webvh-service]], [[didcomm]] +See also: [[verifiable-trust-infrastructure]], [[verifiable-trust-agent]], [[openvtc]], [[affinidi-webvh-service]], [[didcomm]], [[vtafarm]], [[vtafarm-k8s]], [[coordinated-releases]] diff --git a/content/index.md b/content/index.md index eeb3192..566936b 100644 --- a/content/index.md +++ b/content/index.md @@ -1,7 +1,7 @@ --- title: "Wiki Index" type: index -date-updated: 2026-08-19 +date-updated: 2026-09-18 --- # Wiki Index @@ -13,62 +13,74 @@ date-updated: 2026-08-19 ## Concepts ### Identity & DIDs -- [[decentralized-identifiers]] — What DIDs are, DID methods used (webvh, key, peer), resolution +- [[decentralized-identifiers]] — What DIDs are, DID methods used (webvh, key, peer, webs), resolution — now public-hosts-only by default - [[did-webvh]] — did:webvh method: verifiable history, SCIDs, pre-rotation, witnesses, portability -- [[did-types]] — The DID taxonomy: C-DID, M-DID, R-DID, P-DID and their roles (W-DID dropped in WD01) -- [[bip32-key-derivation]] — How all keys derive from one seed phrase via BIP-32/BIP-39 +- [[correlation-scope]] — WD02's replacement for DID types: the holder declares each identifier `pairwise`, `directed` or `public` +- [[did-types]] — The retired DID taxonomy (C/M/R/P-DID, dropped in WD02) and what each maps to now +- [[bip32-key-derivation]] — How all keys derive from one seed phrase via BIP-32/BIP-39 — Ed25519 and ML-DSA alike +- [[post-quantum-cryptography]] — ML-DSA keys, hybrid multi-proof credentials and TSP Rev 3's PQ suite: what is on by default, what is behind a flag, and what was built but unreachable ### Trust & Credentials -- [[decentralized-trust-graph]] — The DTG model: entities as nodes, bidirectional VMC/VRC pairs as edges, trust traversal -- [[verifiable-credentials]] — W3C VCs: what they are, signing, verification, selective disclosure -- [[dtg-credentials-overview]] — The family of DTG credential types and how they fit together -- [[credential-categories]] — The three functional categories: Edge, Invitation, Annotation (VDS moved to a companion spec) -- [[zero-knowledge-proofs]] — Why DTG defaults to ZKP presentation; pairwise vs community-anchored constructions +- [[decentralized-trust-graph]] — The DTG model: entities as nodes (people, communities, rooms), bidirectional credential pairs as edges, per-verifier edge verifiability +- [[verifiable-credentials]] — W3C VCs: what they are, signing, verification, selective disclosure, the formats the vault holds +- [[dtg-credentials-overview]] — The family of DTG credential types at WD02 and where main is taking them +- [[credential-categories]] — Edge (VRC, VMC, VDC) and Annotation credentials, with VIC and VAC outside the categories +- [[zero-knowledge-proofs]] — Why DTG defaults to ZKP presentation; pairwise vs community-anchored constructions; the new chain and shared-subject predicates - [[trust-task-context-binding]] — `taskContext`: binding a credential to the exchange that produced it, without making it proof of the outcome ### Individual Credential Types -- [[membership-credential]] — Community membership and personhood attestation (VMC); two VMCs = one complete edge -- [[personhood-credential]] — PHC: a governance-layer property of VMCs, not a structural subtype -- [[relationship-credential]] — Peer-to-peer trust attestation (VRC); two VRCs = one complete edge -- [[invitation-credential]] — Bootstrap new participants into communities (VIC) +- [[membership-credential]] — VMC: a community grant plus the member's acknowledgement — the second half is consent; two halves = one complete edge +- [[personhood-credential]] — PHC: a governance-layer property of a membership grant, not a structural subtype +- [[relationship-credential]] — Peer-to-peer trust attestation (VRC); two VRCs = one complete edge; pairwise identifiers recommended +- [[delegation-credential]] — VDC (new in WD02): a third edge type — grant plus acceptance — that moves the permission question without answering it +- [[authority-credential]] — VAC (new in WD02): what a holder may do, attenuable down a digest chain; the credential an agent presents to a data room +- [[invitation-credential]] — Bootstrap new participants into communities and rooms (VIC) - [[persona-credential]] — Selective persona disclosure and the "Banksy Maneuver" (VPC) -- [[endorsement-credential]] — Skill and competency endorsements (VEC) -- [[witness-credential]] — Third-party attestations bound to a specific edge and exchange (VWC); one per direction +- [[endorsement-credential]] — VEC: peer endorsement of skills or roles — a statement, never a permission; on main a `dtg:endorses` statement profile +- [[witness-credential]] — Third-party attestations bound to a specific edge and exchange (VWC); `digestMultibase`; becoming a `dtg:witnessed` profile on main +- [[statement-credential]] — VSC (spec main only): one type, predicate profiles, fail-closed verification ### Protocols - [[witnessed-vrc-exchange]] — Five-phase Witnessed Session-Based VRC Exchange protocol -- [[didcomm]] — DIDComm v2: end-to-end encrypted DID-based messaging, mediators -- [[trust-spanning-protocol]] — TSP: the ecosystem's preferred transport (HPKE-Auth + CESR); VTAs can run TSP-only, DIDComm is the interop bridge +- [[peer-identity-vetting]] — Vetted admission: existing members check an applicant in person or on video and sign statements the community counts, without publishing who vouched for whom (OpenVTC Vetting V0) +- [[didcomm]] — DIDComm v2: end-to-end encrypted DID-based messaging, mediators, the no-keylist mediation contract, egress guards +- [[trust-spanning-protocol]] — TSP Rev 3: the ecosystem's transport (HPKE-Base + CESR, relationships first), the five-implementation conformance suite, DIDComm as the separate interop carrier ### Governance & Community - [[trust-registries]] — Authoritative governance: roles, policies, PHC determination -- [[verifiable-trust-community]] — VTCs: structured trust communities with policies -- [[verifiable-trust-network]] — VTNs: federations of VTCs under shared governance +- [[verifiable-trust-community]] — VTCs: structured trust communities with policies, `public` identifiers, vetters, hosted rooms +- [[verifiable-trust-network]] — VTNs: federations of VTCs under shared governance; one anchor set among many - [[first-person-network]] — The First Person vision: self-asserted identity, peer trust — and VGI as Know Your Developer in CI -- [[vta-topology]] — The spec's VTA vocabulary: personal / community, local / cloud, VTA networks, PNM, PNV, VTSP +- [[vta-topology]] — The spec's VTA vocabulary (personal / community, local / cloud, VTA networks, PNM, PNV, VTSP) mapped to the software, including the open-source VTA Farm +- [[data-rooms]] — Credential-governed, MLS-encrypted shared spaces with their own DIDs: shared memory for agents, hosted by a VTC or a tiny `room-host`, portable because there is no member list ### Releases -- [[coordinated-releases]] — The tree-named coordinated releases (Aspen → Banyan → **Cypress**, 2026-08-17): what they are and which versions go together +- [[coordinated-releases]] — The tree-named coordinated releases (Aspen → Banyan → Cypress → **Dogwood** 2026-08-30, silent → Dogwood-R1 → Eucalyptus RC-0 2026-09-17): what they are, why Dogwood was silent, which versions go together ## Entities Each entity page covers both the project's structure (components, role, dependencies) and its recent development activity. ### Primary -- [[verifiable-trust-infrastructure]] — VTI workspace: 26 crates (VTA, VTC, SDKs, mobile core, MCP), the infrastructure stack -- [[verifiable-trust-agent]] — VTA: key management, signing oracle, credential vault (DI/BBS/SD-JWT/mdoc), approvals, TEE support -- [[openvtc]] — OpenVTC TUI: user-facing multi-community client (v0.3.0 — agent names, TSP, async join) -- [[dtg-credentials]] — dtg-credentials library: DTG credential implementation (0.2.0 tracks spec WD01) +- [[verifiable-trust-infrastructure]] — VTI workspace: 31 crates (VTA, VTC, SDKs, mobile core, MCP, persona store, data rooms, `room-host`), the infrastructure stack +- [[verifiable-trust-agent]] — VTA: key management, signing oracle, credential vault (DI/BBS/SD-JWT/mdoc), persona and app-state stores, room key custody, approvals, TEE support +- [[openvtc]] — OpenVTC TUI: user-facing multi-community client (0.3.1 on Eucalyptus RC-0 — recoverable installs, My Identity pane, vetting V0, TSP Rev 3) +- [[dtg-credentials]] — dtg-credentials library: DTG credential implementation (0.9.1 tracks spec WD02; VAC/VDC chains, `digestMultibase`) ### Specifications -- [[dtg-credential-spec]] — ToIP DTG Core Credentials specification — v1.0 Working Draft 01 in its new `dtgwg-cred-spec` repo +- [[dtg-credential-spec]] — ToIP DTG Core Credentials specification — Working Draft 02 (2026-09-07), main heading for 0.4.0 with Statement Credentials ### Secondary (Building Blocks) -- [[affinidi-tdk]] — Affinidi TDK: DID resolution, messaging (TSP + DIDComm v2/v1), reliable delivery layer, agent names, crypto primitives -- [[affinidi-webvh-service]] — did-hosting-service: did:webvh / did:web hosting, three transports, `/@name` agent-name resolution -- [[didwebvh-rs]] — didwebvh-rs: reference did:webvh Rust implementation -- [[vti-setup]] — vti-setup: persona-organized guides for the full VTI stack, pinned to Cypress; Kubernetes + VTA Farm deploy docs coming -- [[verifiable-git-infrastructure]] — VGI: `did-git-sign` + the `verify-trust` GitHub Action — commit trust for DIDs against a VTC Trust Registry -- [[vta-browser-plugin]] — VTA Wallet browser plugin: passkeys ↔ VTA DIDs, web login, in-browser consent/step-up approver +- [[affinidi-tdk]] — Affinidi TDK: DID resolution (webvh, webs, scid), messaging (TSP Rev 3 + DIDComm v2/v1), reliable delivery, multi-mediator relay, `net-guard` +- [[affinidi-webvh-service]] — did-hosting-service: did:webvh / did:web / did:webs hosting, three transports, Trust-Task auth on every binding +- [[didwebvh-rs]] — didwebvh-rs: reference did:webvh Rust implementation (0.7.0: public-hosts-only resolution by default) +- [[vti-setup]] — vti-setup: persona-organized guides for the full VTI stack, explore stream pinned to Dogwood with every download verified; deploy stream → the VTA Farm repos +- [[verifiable-git-infrastructure]] — VGI: `did-git-sign` + the `verify-trust` GitHub Action — commit trust for DIDs via a `Signed-by-DID:` trailer, verified against a VTC Trust Registry (0.4.12) +- [[vta-browser-plugin]] — VTA Wallet browser plugin: passkeys ↔ VTA DIDs, web login, consent approver, management console, persona map, data-rooms pane (pnm-core 0.9.1) - [[rp-sdk-js]] — `@openvtc/rp-sdk`: server-side verification of wallet logins for relying parties -- [[vti-didcomm-js]] — browser-side DIDComm v2 subset used by the wallet +- [[vti-didcomm-js]] — browser-side DIDComm v2 subset used by the wallet, with an egress policy (`net-guard`) and TSP Rev 3 framing (0.10.1) + +### VTA Farm (hosted VTAs on Kubernetes) +- [[vtafarm]] — VTA Farm portal: passkey sign-in, create and manage a hosted Personal VTA or Full Stack, admin console (v0.4.0) +- [[vtafarm-api]] — VTA Farm backend: per-user namespaces, Vault-sealed seeds, `vta setup` / `import-did` as Kubernetes Jobs, platform stack, sharing, custom domains (v0.4.0) +- [[vtafarm-k8s]] — OpenTofu stacks for a Farm on Hetzner: k3s + Rancher → RKE2 → cert-manager / Longhorn / two Vaults → the apps; thirteen runbooks diff --git a/content/log.md b/content/log.md index 1966650..d2ac2e2 100644 --- a/content/log.md +++ b/content/log.md @@ -1,7 +1,7 @@ --- title: "Wiki Log" type: log -date-updated: 2026-08-19 +date-updated: 2026-09-18 --- # Wiki Log @@ -10,6 +10,22 @@ A record of major wiki updates: new sources ingested, significant concept and en --- +## [2026-09-18] August–September refresh — the silent Dogwood release, the VTA Farm goes open source, spec Working Draft 02, data rooms + +The ecosystem shipped **`Dogwood`** on 2026-08-30 — its fourth coordinated release and the first to go out *silently*: tag-only, no GitHub Release, no announcement. Reading the month between Cypress and Dogwood explains why. Almost nothing in it is user-visible: a Trust-Task conformance sweep that drove the VTC's schema drift to zero and then enforced the framework at the VTA's dispatch spine (every client identified and signing, replay and freshness checked), the member-side half of the membership edge finally landing in software, OpenVTC secrets no longer vanishing on reboot, retries made idempotent, and dependencies moving a long way (trust-tasks 0.9 → 0.17, vta-sdk 0.25 → 0.32). Two days of production findings produced a `Dogwood-R1` re-cut — federated mediator relay, a wallet-wide inbox that silently lost consent prompts, cross-mediator TSP routing — recorded repo by repo on [[coordinated-releases]]. The first **`Eucalyptus`** release candidate was tagged on 2026-09-17. + +**The Kubernetes promise is kept.** For months this wiki said the sysop *deploy* stream — Kubernetes, HashiCorp Vault, the VTA Farm — was "coming soon". It shipped in August as three open-source repos under the `ic3software` org, now Secondary sources with their own entity pages: [[vtafarm]] (the portal), [[vtafarm-api]] (the Go/Kubernetes provisioning backend) and [[vtafarm-k8s]] (the OpenTofu cluster build on Hetzner). The security model is the story: one Kubernetes namespace per user, every VTA's master seed sealed in Vault under a role only that namespace can assume, an operator whose own Vault policy cannot read a seed, and an admin key generated on the user's machine and imported — the Farm hosts your agent but never controls it. [[vta-topology]], [[vti-setup]], [[verifiable-trust-agent]] and the [[overview]] were updated accordingly. + +**The spec moved — twice.** The [[dtg-credential-spec|DTG Core Credentials spec]] tagged **Working Draft 02** (2026-09-07): the four DID types are retired in favour of holder-declared [[correlation-scope]] (pairwise / directed / public); the membership pair is fully specified as a grant plus a consent-carrying acknowledgement; cross-credential digests become `digestMultibase`; and two new credential types arrive — the [[delegation-credential|Verifiable Delegation Credential]] (a third edge type) and the [[authority-credential|Verifiable Authority Credential]] (attenuable permission chains). The spec's main branch is already further, folding endorsement and witness into a single [[statement-credential|Statement Credential]] with predicate profiles. [[dtg-credentials]] went through eight breaking releases in a month (0.2.0 → 0.9.1) to track it, closing the digest-encoding divergence flagged last cycle. Eighteen concept pages were revised; [[did-types]] is now a history page. + +**A third kind of node.** [[data-rooms|Data rooms]] — credential-governed, MLS-encrypted shared spaces with their own DIDs, portable between hosts because there is deliberately no member list, aimed at shared memory for AI agents — landed in the VTI in the fortnight after Dogwood, with four new crates, a standalone `room-host`, a browser member in WebAssembly and a rooms console in the [[vta-browser-plugin|VTA Wallet]]. + +**Also this cycle:** [[trust-spanning-protocol|TSP Rev 3]] adopted as a flag day across every Rust component, with a new cross-implementation conformance suite covering five implementations (including new Go and Dart ports); [[post-quantum-cryptography|post-quantum ML-DSA keys]] in the VTA and hybrid multi-proof credentials from the VTC; the beginnings of [[peer-identity-vetting]]; a cross-repo security sweep (public-hosts-only DID resolution, egress guards, a hash-chained VTA audit log, SHA-pinned CI); VGI's `Signed-by-DID:` commit trailer; the browser wallet's management console and persona map; and every vti-setup download now verified. + +New pages: 3 entities, 7 concepts. Updated: 13 entities, 25 concepts, overview, index, sources. + +--- + ## [2026-08-19] July–August refresh — the Cypress release, the spec moves house, five new sources **Cypress is out.** On 2026-08-17 the ecosystem cut **`Cypress`**, its third coordinated, cross-project release and the first to go through formal release candidates — tagged across [[verifiable-trust-infrastructure|VTI]] (vta-service 0.17 / vta-sdk 0.25), [[openvtc|OpenVTC]] (v0.3.0 → 0.3.1), [[dtg-credentials]] (0.2.0), the [[affinidi-tdk|Affinidi TDK]] (mediator 0.18.19), [[affinidi-webvh-service|did-hosting-service]] (0.8.x) and [[verifiable-git-infrastructure|VGI]] (0.4.5), with the [[vti-setup]] guides re-verified and pinned to it. A new page, **[[coordinated-releases]]**, explains the Aspen → Banyan → Cypress convention and lists exactly which versions go together. diff --git a/content/overview.md b/content/overview.md index 0992baa..2ba923d 100644 --- a/content/overview.md +++ b/content/overview.md @@ -2,8 +2,8 @@ title: "OpenVTC Ecosystem Overview" type: overview tags: [openvtc, vti, trust-graph, decentralized-identity, first-person-network] -date-updated: 2026-08-19 -sources: [verifiable-trust-infrastructure, openvtc, dtg-credentials, dtg-credential-spec, affinidi-tdk, affinidi-webvh-service, didwebvh-rs, vti-setup, verifiable-git-infrastructure, vta-browser-plugin, rp-sdk-js, vti-didcomm-js] +date-updated: 2026-09-18 +sources: [verifiable-trust-infrastructure, openvtc, dtg-credentials, dtg-credential-spec, affinidi-tdk, affinidi-webvh-service, didwebvh-rs, vti-setup, verifiable-git-infrastructure, vta-browser-plugin, rp-sdk-js, vti-didcomm-js, vtafarm, vtafarm-api, vtafarm-k8s] --- # OpenVTC Ecosystem Overview @@ -28,37 +28,40 @@ Here's how the pieces fit together, from bottom to top: At the foundation, every participant needs a cryptographically secure identity — a [[decentralized-identifiers|DID]]. The ecosystem uses [[did-webvh|did:webvh]], a DID method that gives you a portable, self-certifying identifier with a verifiable history of changes. You host your DID document on your own domain or a domain controlled by the provider of your choice, and anyone can verify its entire history of updates, key rotations, and witness attestations. -The [[didwebvh-rs]] library provides the Rust implementation of this DID method, and the [[affinidi-webvh-service|did-hosting-service]] runs the infrastructure for hosting and resolving these DIDs at scale. Because a DID is unreadable to humans, the ecosystem added **agent names** in July 2026 — `example.com/@alice` — a shortcut that redirects to a DID and is only ever shown once the DID document claims the name back in `alsoKnownAs`. +The [[didwebvh-rs]] library provides the Rust implementation of this DID method, and the [[affinidi-webvh-service|did-hosting-service]] runs the infrastructure for hosting and resolving these DIDs at scale. Because a DID is unreadable to humans, the ecosystem added **agent names** in July 2026 — `example.com/@alice` — a shortcut that redirects to a DID and is only ever shown once the DID document claims the name back in `alsoKnownAs`. A person may hold several identifiers with different reach; since Working Draft 02 of the spec the holder *declares* each one's [[correlation-scope]] — pairwise, directed or public — rather than the identifier carrying a fixed type. ### Layer 2: Key Management and Signing Your DID is backed by cryptographic keys, but managing keys securely is hard. The [[verifiable-trust-agent|Verifiable Trust Agent (VTA)]] solves this — it's an always-on service that manages your keys, signs things on your behalf, and can run inside hardware-isolated enclaves so your key material never touches an unprotected environment. -The VTA is the heart of the [[verifiable-trust-infrastructure|Verifiable Trust Infrastructure (VTI)]]. It acts as a **signing oracle**: applications ask it to sign credentials, DID updates, or messages, and it handles the cryptography. Keys derive from a single seed phrase using [[bip32-key-derivation|BIP-32 derivation]], so backing up one mnemonic protects everything (with non-extractable internal keys as the deliberate exception). It is also the holder's **credential vault** — W3C Data Integrity, BBS, SD-JWT and, since August 2026, ISO mdoc. For most people the VTA runs *in the cloud* — self-hosted, or managed on the project's **VTA Farm** — while thin *Personal Network Manager* surfaces (the `pnm` CLI, the mobile Authenticator, the [[vta-browser-plugin|VTA Wallet browser plugin]]) approve and act on its behalf; see [[vta-topology]] for the vocabulary. +The VTA is the heart of the [[verifiable-trust-infrastructure|Verifiable Trust Infrastructure (VTI)]]. It acts as a **signing oracle**: applications ask it to sign credentials, DID updates, or messages, and it handles the cryptography. Keys derive from a single seed phrase using [[bip32-key-derivation|BIP-32 derivation]], so backing up one mnemonic protects everything (with non-extractable internal keys as the deliberate exception) — and since September 2026 that includes [[post-quantum-cryptography|post-quantum ML-DSA keys]] alongside Ed25519. It is also the holder's **credential vault** — W3C Data Integrity, BBS, SD-JWT and ISO mdoc — and, increasingly, the holder's *system of record*: a persona store, an app-state store, agent memory, and the key custody for [[data-rooms|data rooms]] all live in the VTA. + +For most people the VTA runs *in the cloud* — self-hosted, or managed on the **VTA Farm** — while thin *Personal Network Manager* surfaces (the `pnm` CLI, the mobile Authenticator, the [[vta-browser-plugin|VTA Wallet browser plugin]]) approve and act on its behalf; see [[vta-topology]] for the vocabulary. In August 2026 the Farm itself became open source as three repos: the [[vtafarm|portal]], the [[vtafarm-api|Go/Kubernetes provisioning backend]] and the [[vtafarm-k8s|OpenTofu cluster build]]. Its security model is the point: one Kubernetes namespace per user, every master seed sealed in HashiCorp Vault under a role only that namespace can assume, an operator whose own Vault policy cannot read a seed, and an admin key that is generated on the user's machine and imported — so the Farm hosts your agent but never controls it. ### Layer 3: Secure Communication -Participants communicate over end-to-end encrypted, DID-bound messaging routed through mediators, so participants don't need to know each other's IP addresses. The [[affinidi-tdk|Affinidi Trust Development Kit]] provides the messaging infrastructure, including a mediator service. The ecosystem's preferred transport is now the lean ToIP [[trust-spanning-protocol|Trust Spanning Protocol (TSP)]], with [[didcomm|DIDComm v2]] as the interop fallback — the mediator is dual-protocol on one connection, a VTA can run TSP-only, and communities publish which transports they offer. Every protocol exchange is expressed as a **Trust Task** — a versioned JSON document with a canonical `trusttasks.org/spec/*` URI — so the same operation works over TSP, DIDComm, or plain HTTPS. Underneath, a reliable **delivery layer** (durable outbox, delivery evidence, ack-only-after-handoff, stored-mail pickup on connect) arrived in July 2026 to close a class of silently lost messages; and for the wider Aries/Credo world the mediator also speaks DIDComm v1. +Participants communicate over end-to-end encrypted, DID-bound messaging routed through mediators, so participants don't need to know each other's IP addresses. The [[affinidi-tdk|Affinidi Trust Development Kit]] provides the messaging infrastructure, including a mediator service. The ecosystem's transport is the lean ToIP [[trust-spanning-protocol|Trust Spanning Protocol (TSP)]] — since September 2026 at **Rev 3**, adopted as a flag day across every Rust component, with relationships formed before any exchange and a cross-implementation conformance suite covering five implementations in Rust, TypeScript, Go and Dart. [[didcomm|DIDComm v2]] remains as the interop carrier on the same mediator (no longer bridged to TSP), and for the wider Aries/Credo world the mediator also speaks DIDComm v1. Every protocol exchange is expressed as a **Trust Task** — a versioned JSON document with a canonical `trusttasks.org/spec/*` URI — so the same operation works over TSP, DIDComm, or plain HTTPS; since the Dogwood release those documents are validated, signed by an identified client and replay-guarded at the dispatch spine. Underneath, a reliable **delivery layer** (durable outbox, delivery evidence, ack-only-after-handoff, stored-mail pickup on connect) closes a class of silently lost messages. ### Layer 4: Trust Credentials -Trust relationships are expressed as [[dtg-credentials-overview|Decentralized Trust Graph (DTG) Credentials]] — six Verifiable Credential types defined by the Trust Over IP Foundation in the [[dtg-credential-spec|DTG Core Credentials specification]], which in July 2026 became a formal ToIP deliverable (v1.0 Working Draft 01) in its own repo. They fall into [[credential-categories|three functional categories]], with the foundational credentials being: +Trust relationships are expressed as [[dtg-credentials-overview|Decentralized Trust Graph (DTG) Credentials]] — Verifiable Credential types defined by the Trust Over IP Foundation in the [[dtg-credential-spec|DTG Core Credentials specification]], a formal ToIP deliverable whose **Working Draft 02** (September 2026) reshaped the family around what implementers had learned. The foundational credentials are: -- **[[membership-credential|Membership Credentials]]** — proof that you are a real, unique *human* within a community (the ecosystem's answer to AI agent impersonation). Membership is verified through a bi-directional pair of VMCs. +- **[[membership-credential|Membership Credentials]]** — proof that you are a real, unique *human* within a community (the ecosystem's answer to AI agent impersonation). Membership is a bi-directional pair: a grant from the community and an acknowledgement from the member — and the acknowledgement half only actually landed in software in the Dogwood release. - **[[relationship-credential|Relationship Credentials]]** — peer-to-peer trust attestations between two people. A relationship is verified through a bi-directional pair of VRCs (one each direction forms a complete edge). -- **[[endorsement-credential|Endorsement Credentials]]**, **[[witness-credential|Witness Credentials]]**, and **[[persona-credential|Persona Credentials]]** — annotations that strengthen existing edges with skill endorsements, third-party attestations (now bound to the specific edge and the specific exchange — see [[trust-task-context-binding]]), and persona linking +- **[[delegation-credential|Delegation]]** and **[[authority-credential|Authority Credentials]]** — new in WD02: a delegation is a third kind of edge (a grant plus a required acceptance) that moves the permission question without answering it; an authority credential is a statement *to a verifier* of what a holder may do, attenuable by the holder down a digest-linked chain — which is what lets a person's VTA hand its AI agent a narrow, hours-scoped grant. +- **[[endorsement-credential|Endorsement]]**, **[[witness-credential|Witness]]**, and **[[persona-credential|Persona Credentials]]** — annotations that strengthen existing edges with skill endorsements, third-party attestations bound to the specific edge and exchange ([[trust-task-context-binding]]), and persona linking. On the spec's main branch, endorsement and witness are becoming predicate profiles of a single [[statement-credential|Statement Credential]]. -These credentials form the [[decentralized-trust-graph|Decentralized Trust Graph]] — a graph whose nodes are entities (people, devices, agents, communities) and whose edges are credential pairs. Anyone can trace trust paths between participants. Communities can federate into [[verifiable-trust-network|Verifiable Trust Networks (VTNs)]], and [[trust-registries|trust registries]] provide authoritative records of which entities are authorized to perform specific actions within a community. +These credentials form the [[decentralized-trust-graph|Decentralized Trust Graph]] — a graph whose nodes are entities (people, devices, agents, communities, and now [[data-rooms|rooms]]) and whose edges are credential pairs. Anyone can trace trust paths between participants. Communities can federate into [[verifiable-trust-network|Verifiable Trust Networks (VTNs)]], and [[trust-registries|trust registries]] provide authoritative records of which entities are authorized to perform specific actions within a community. The human root of the whole graph — a community checking that a new member is a real person — is being made explicit as [[peer-identity-vetting]]. ### Layer 5: The User Experience -[[openvtc|OpenVTC]] is the user-facing TUI that ties it all together for end users. It walks you through binding to your VTA, minting personas (with agent names), establishing trust relationships with others, and participating in trust communities — including **multiple** trust communities simultaneously, each with its own persona and lifecycle. The multi-community pivot completed in June 2026 (`Banyan`); **v0.3.0** (August 2026, in the `Cypress` release) made the join ceremony robust to the realities of asynchronous messaging — joining by DID or agent name, over TSP or DIDComm, with stored mail collected on connect and unanswered joins polled rather than waited on. Behind the scenes, it orchestrates the VTA, TSP/DIDComm messaging on the reliable delivery layer, and Trust-Task exchanges. +[[openvtc|OpenVTC]] is the user-facing TUI that ties it all together for end users. It walks you through binding to your VTA, minting personas (with agent names), establishing trust relationships with others, and participating in trust communities — including **multiple** trust communities simultaneously, each with its own persona and lifecycle. **v0.3.0** (August 2026, `Cypress`) made the join ceremony robust to asynchronous messaging; the Dogwood cycle made an install *recoverable* from what the VTA holds and made the VTA the system of record; the Eucalyptus cycle is adding a *My Identity* pane (facts, faces and what each persona wears), per-community VTA sub-contexts, and the first cut of peer identity vetting. -For the **Know Your Developer** case specifically, [[verifiable-git-infrastructure|Verifiable Git Infrastructure (VGI)]] — split out of OpenVTC in July 2026 — signs git commits with a VTA-held key and lets a project's CI verify, against the community's Trust Registry, that every commit in a pull request was signed by a DID the community currently authorises. For **the web**, the [[vta-browser-plugin|VTA Wallet browser plugin]] lets you log into websites as the controller of your VTA DID (passkey ceremony in the browser, keys never leave the VTA), with [[rp-sdk-js]] for the site's server side and [[vti-didcomm-js]] as the browser DIDComm stack. +For the **Know Your Developer** case specifically, [[verifiable-git-infrastructure|Verifiable Git Infrastructure (VGI)]] signs git commits with a VTA-held key — the signer named by a `Signed-by-DID:` trailer since Dogwood — and lets a project's CI verify, against the community's Trust Registry, that every commit in a pull request was signed by a DID the community currently authorises. For **the web**, the [[vta-browser-plugin|VTA Wallet browser plugin]] lets you log into websites as the controller of your VTA DID (passkey ceremony in the browser, keys never leave the VTA), with [[rp-sdk-js]] for the site's server side and [[vti-didcomm-js]] as the browser DIDComm stack; the wallet now also carries a management console, a persona identity map and a data-rooms pane. For **shared context**, [[data-rooms|data rooms]] give a team, a working group or a family a credential-governed, MLS-encrypted space that its members' agents can also recall from — hosted by a community or by a tiny standalone `room-host` that is trusted for availability only. -On mobile, the [[verifiable-trust-infrastructure|VTI]] now ships two thin native apps — **Authenticator** (a holder's pocket approver for AAL step-up over DIDComm v2) and **PNM mobile** (mobile counterpart of the `pnm` operator CLI) — both built on a shared Rust UniFFI engine, `vta-mobile-core`. +On mobile, the [[verifiable-trust-infrastructure|VTI]] ships two thin native apps — **Authenticator** (a holder's pocket approver for AAL step-up) and **PNM mobile** (mobile counterpart of the `pnm` operator CLI) — both built on a shared Rust UniFFI engine, `vta-mobile-core`. -And for anyone who wants to stand the whole stack up themselves — from a throwaway learning VM to a production deployment — the [[vti-setup]] repo provides tested, version-pinned setup guides organized by persona (developer, community manager, sysop), pinned since August 2026 to the **`Cypress`** coordinated release ([[coordinated-releases]]). Kubernetes deployment instructions, alongside the managed VTA Farm, are coming soon — so that deploying a VTA for an individual becomes straightforward either way. +And for anyone who wants to stand the whole stack up themselves, the [[vti-setup]] repo provides tested, version-pinned setup guides organized by persona (developer, community manager, sysop), pinned since 2026-08-30 to the **`Dogwood`** coordinated release ([[coordinated-releases]]), with every download now verified. The production path — Kubernetes, Vault, and the Farm — is the [[vtafarm-k8s]] repo: anyone with a Hetzner project and a DNS zone can build their own Farm from it. ## The First Person Network @@ -68,16 +71,17 @@ The name "first person" is deliberate — this is identity asserted by *you*, no ## Where Things Are Heading -As of August 2026 the ecosystem has just cut **`Cypress`** — its third coordinated, cross-project release and the first to go through formal release candidates ([[coordinated-releases]]). Cypress is the clearest statement yet of what the stack *is*: VTA 0.17 / vta-sdk 0.25, OpenVTC 0.3, dtg-credentials 0.2 (tracking the spec's first formal Working Draft), TDK mediator 0.18 with the reliable delivery layer and agent names, did-hosting-service 0.8, VGI 0.4.5, didwebvh-rs 0.6 — all on trust-tasks 0.9, all exercised together, all pinned by the setup guides. The frontier has moved accordingly: +As of mid-September 2026 the ecosystem has shipped **`Dogwood`** (2026-08-30) — a deliberately *silent* release, tag-only, whose whole point was behind the scenes — re-cut it as `Dogwood-R1` after two days of production findings, and tagged the first release candidate of **`Eucalyptus`** (2026-09-17), which is the opposite in character: the largest feature window the stack has had ([[coordinated-releases]]). The frontier has moved on several fronts at once: -- **Convergence, not expansion.** The July–August work across every repo was about making one shape out of many: every wire operation is now a canonical Trust Task (the VTI retired its legacy REST surfaces with a usage metric gating their deletion, the VTC its `openvtc/vtc` bindings, did-hosting its own URIs, the TDK mediator its 19-task admin family); three approval mechanisms became one runtime-manageable model with a break-glass; the `vta-service` monolith became eleven subsystem crates; and publishing moved to release-plz. A codebase preparing for 1.0. -- **TSP is the transport, DIDComm the bridge.** No longer "preferred in principle": the VTA can run TSP-only, communities publish their transports, registries are reached by DID over TSP, joins run over TSP, the browser wallet has a pure-TypeScript TSP stack. DIDComm's role is interop — including, newly, DIDComm *v1* for Aries/Credo wallets. -- **The spec is formal — and being shaped by implementation.** The DTG Core Credentials spec is a ToIP deliverable at Working Draft 01: three categories not four, four DID types not five, a new `taskContext` binding, and a Witness Credential that now *must* name the exchange and the edge it attests — changes driven directly by implementer feedback (a wallet's witness server) and already reflected in dtg-credentials 0.2.0, with one flagged digest-encoding divergence still to resolve. Two companion specs (verifiable data structures; trust-task protocols) are announced. -- **Names for humans, trust for git, login for the web.** Agent names (`example.com/@alice`) landed across the TDK, did-hosting, the VTA and OpenVTC in a single fortnight; VGI turns Know Your Developer into a required CI check; the browser wallet is heading for the Chrome Web Store with its core library about to become a standalone VTA client. -- **The VTA holds more kinds of things for more kinds of holders.** ISO mdoc joins DI / BBS / SD-JWT in the vault; non-extractable keys and a three-gate signing-oracle guarantee harden the oracle; the phone verifies request proofs on-device before prompting; and the Nitro enclave no longer bakes tenant config into the image, so one attested image can serve a fleet. -- **Deployment is getting easier.** The VTA Farm is open self-signup; pre-built binaries track the latest tagged release; and the next vti-setup milestone is **Kubernetes deployment instructions alongside the VTA Farm**, so that an individual's VTA — managed or self-run — is a short path rather than a weekend. +- **Dogwood made the wire honest.** A Trust-Task conformance sweep drove schema drift to zero and then enforced the framework at the dispatch spine — every client identified and signing, replay and freshness checked, error messages no longer an oracle. The membership edge finally closed end to end. Secrets stopped vanishing on reboot. Retries became idempotent. None of it is a feature; all of it is what the next features stand on. +- **Kubernetes and the Farm are delivered.** The promise recorded here for months — Kubernetes deployment instructions alongside the VTA Farm — was kept in August: three open-source repos ([[vtafarm]], [[vtafarm-api]], [[vtafarm-k8s]]) that let anyone run a Verifiable Trust Service Provider with the same seed-in-Vault, namespace-per-user, admin-key-never-leaves-you model as the public Farm. +- **TSP Rev 3, everywhere, verified.** The transport moved to the current ToIP revision in one cut, with relationships as a first-class, persisted, recoverable state, multi-hop nested routing for metadata privacy, and a conformance suite that pins five implementations to the spec's own test vectors — including new Go and Dart ports. +- **The spec caught up with practice — and moved ahead of it.** WD02 replaced the DID-type taxonomy with holder-declared [[correlation-scope]], specified the membership pair exactly, changed digest encoding to `digestMultibase`, and added [[delegation-credential|Delegation]] and [[authority-credential|Authority]] credentials; dtg-credentials went through eight breaking releases in a month to track it. The main branch is already further: a single [[statement-credential|Statement Credential]] absorbing endorsement and witness. +- **A third kind of node.** [[data-rooms|Data rooms]] — a room with its own DID, issuing its own credentials, encrypted under MLS with the member's VTA as the key custodian, and portable between hosts because there is deliberately no member list — are the Eucalyptus headline, built in a fortnight on top of everything Dogwood fixed. They are aimed squarely at shared memory for AI agents. +- **Post-quantum, for real this time.** ML-DSA keys derive from the same seed, DID templates carry per-slot algorithms, the VTC issues hybrid multi-proof credentials, and TSP Rev 3 carries a PQ suite — after the discovery that the PQ code path had been "built, tested, unreachable" behind a missing feature flag. +- **Hardening as a standing discipline.** A cross-repo security sweep (2026-09-10 → 09-12) closed the same class of bug everywhere at once: public-hosts-only DID resolution, egress guards in the mediator and the browser stack, a hash-chained audit log in the VTA, SHA-pinned CI, secrets never read from environment overrides in release builds. -The direction of travel is unchanged but closer: a fully self-contained, publicly deployable trust infrastructure that any community can adopt — with the phone, the browser, the git forge and the AI agent as first-class holders and approvers of credentials, TSP as the common transport, and a formal ToIP specification underneath. +The direction of travel is unchanged but closer: a fully self-contained, publicly deployable trust infrastructure that any community can adopt — with the phone, the browser, the git forge, the AI agent and now the shared room as first-class holders of credentials, TSP as the common transport, and a formal ToIP specification underneath. For a release-by-release timeline of how the ecosystem got here — including each project's recent activity log — see the [[log|wiki log]] and the **Recent Development** section on each entity page. @@ -86,7 +90,7 @@ For a release-by-release timeline of how the ecosystem got here — including ea This wiki is organized into: - **[[index|Index]]** — catalog of all pages -- **Concepts** — explanations of key ideas ([[decentralized-identifiers]], [[verifiable-credentials]], [[decentralized-trust-graph]], etc.) -- **Entities** — the projects, components, and specifications ([[verifiable-trust-agent]], [[openvtc]], [[affinidi-webvh-service]], [[dtg-credential-spec]], [[verifiable-git-infrastructure]], etc.). Each entity page carries both conceptual structure and a Recent Development log. For "which versions go together?", see [[coordinated-releases]]. +- **Concepts** — explanations of key ideas ([[decentralized-identifiers]], [[verifiable-credentials]], [[decentralized-trust-graph]], [[data-rooms]], etc.) +- **Entities** — the projects, components, and specifications ([[verifiable-trust-agent]], [[openvtc]], [[affinidi-webvh-service]], [[dtg-credential-spec]], [[vtafarm]], etc.). Each entity page carries both conceptual structure and a Recent Development log. For "which versions go together?", see [[coordinated-releases]]. Start with the concepts if you want to understand the "why." Start with the entities if you want to understand the "what." The pages are heavily cross-linked — follow the threads that interest you.