Skip to content

feat(relationships): default to a pairwise R-DID, and say what the choice costs - #254

Merged
stormer78 merged 1 commit into
mainfrom
worktree-rdid-default
Aug 23, 2026
Merged

feat(relationships): default to a pairwise R-DID, and say what the choice costs#254
stormer78 merged 1 commit into
mainfrom
worktree-rdid-default

Conversation

@stormer78

Copy link
Copy Markdown
Contributor

Client-side half of #241 (RAHP finding OVTC-RAHP-04). Makes pairwise the
default and puts the trade-off in front of the operator at the moment they
choose.

What changed

Request formgenerate_r_did now defaults to true. The toggle reads
Contact you as: Pairwise R-DID (recommended) / Your persona DID and renders
the consequence of whichever is selected, rather than a bare Yes/No.

Inbox accept — the convenient key is now the private one:

Key Before After
a accept with persona DID accept with pairwise R-DID
A accept with R-DID accept with R-DID (unchanged)
p accept with persona DID

A deliberately keeps its old meaning, so anyone with existing muscle memory
gets the private outcome they always got. No keystroke that used to produce an
R-DID now produces a persona DID.

Docsdocs/relationships-vrcs.md gains a Choosing your identifier
comparison table and drops the --generate-did example. That flag, and the
whole openvtc relationships subcommand, have not existed since the v0.2.0 TUI
rewrite (1f31455); the file also contradicted itself, with a diagram showing
pairwise as the normal flow and prose calling it optional. Added a note that
the remaining command examples are stale — rewriting the guide for the TUI is a
separate job.

Regression guardestablished_pairwise_relationship_never_pings_from_the_persona_did
pins criterion 3: an established relationship's ping takes its from, its
recipient and its listener from the relationship record. The property held
already; nothing tested it. Verified the test fails when prepare_ping is made
to read persona_did_arc() instead.

What this does not do

It does not satisfy acceptance criterion 4. Two counterparties can still
correlate you, for two reasons that are not client-side:

  • The three handshake messages (request/accept/finalise) are routed
    persona-to-persona by design — the mediator has to route them before a
    pairwise channel exists.
  • The VRC issuer must be the persona DID, because the VTC's
    relationships.rego requires issuer and subject to be current ACL-listed
    members. A VRC issued under a pairwise R-DID is rejected today.

Raised upstream as OpenVTC/verifiable-trust-infrastructure#1054. The commit
message and the docs both say so, so a flipped boolean does not later read as
"correlation solved".

Also corrected on the issue: proposal 2 (CLI opt-out flag) has nothing to
change, since the CLI it refers to is gone.

Checks

cargo fmt --check, cargo clippy --workspace --all-targets -D warnings,
cargo test --workspace (all green), and
RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps.

Refs #241

…oice costs

The persona DID is a published, resolvable `did:webvh` that often carries a
verified agent name. Reusing it across contacts makes correlation a lookup
rather than an inference — two counterparties compare a string and resolve it
to a named identity. Yet pairwise was opt-in, on a decision whose consequences
were invisible at the moment it was made: the request form defaulted to `No`,
and in the inbox the convenient unshifted `a` was the linkable accept while the
private one hid behind Shift+A.

Flip both. A new request mints an R-DID unless the operator turns it off, and
`a` now accepts pairwise. `A` stays wired to the pairwise accept too, so anyone
with the old muscle memory keeps getting the private outcome they always got;
the persona-DID accept moves to `p`, where it names itself.

Neither surface makes the choice silently. The request form's toggle reads
"Contact you as: Pairwise R-DID / Your persona DID" and spells out the
consequence of whichever is selected; the inbox detail states both outcomes
above the footer. The docs gain a comparison table, and lose the `--generate-did`
invocation for a CLI subcommand that has not existed since the v0.2.0 TUI
rewrite.

Also guards the property the default depends on: an established relationship
must never revert to the persona DID. That holds today and nothing tested it —
`established_pairwise_relationship_never_pings_from_the_persona_did` pins the
message `from`, the recipient and the listener to the relationship record.

Scope, deliberately: this does not make two counterparties unable to correlate
you. The three handshake messages are routed persona-to-persona because the
mediator must route them before a pairwise channel exists, and the VRC issuer
must be the persona DID because the VTC's `relationships.rego` requires issuer
and subject to be current ACL-listed members. Full pairwise operation is a
protocol change, tracked in OpenVTC/verifiable-trust-infrastructure#1054.

Refs #241

Signed-off-by: Glenn Gore <glenn.g@affinidi.com>
@stormer78
stormer78 requested a review from a team as a code owner August 23, 2026 02:28
@stormer78
stormer78 merged commit 3529cf4 into main Aug 23, 2026
14 checks passed
@stormer78
stormer78 deleted the worktree-rdid-default branch August 23, 2026 02:37
stormer78 added a commit that referenced this pull request Aug 23, 2026
…he persona (#255)

The last half of #241, and the half that mattered most.

Relationship communication has defaulted to a pairwise R-DID since #254, but
the credential it produces did not. `prepare_accept_vrc_request` minted the VRC
with `issuer = persona_did`, signed with the persona key. The subject was
already the peer's R-DID, so only the issuer half was persona-attributed — and
that was enough: the pairwise channel led straight back to the persona.

It is the worst place to leak it. The handshake DIDs are observed once, by the
mediator, at establishment. The VRC is the durable artifact both parties retain
and may publish to a community trust graph, where it correlates every
relationship a persona holds for as long as anyone keeps a copy. #241 said the
same: "The handshake persona DID is observed once; a published VRC correlates
indefinitely."

This could not be fixed here before now. The VTC pinned a published VRC's
issuer to the authenticated session DID, so a VRC issued under a relationship
DID was rejected outright — the only enforcement that existed forbade pairwise
identifiers rather than requiring them. Lifted in
OpenVTC/verifiable-trust-infrastructure#1061, which separates membership (proven
by the session) from the identifier the credential carries.

Both ends move together, so this is breaking on the wire:

- **Issuance** signs as whichever identity the relationship uses, keyed on
  `is_persona_did` — the same discriminator `listener_id_for_did` routes sends
  on. A relationship established without a dedicated R-DID still signs with the
  persona key, unchanged.
- **`vet_vrc_issued` gate 2** now requires the issuer to be the sender's DID in
  *this* relationship rather than their persona DID. That is strictly tighter
  as well as more private: gate 1 already matched the authenticated DIDComm
  sender against the same field, so the credential is pinned to the channel it
  arrived on instead of to a persona that could have issued it anywhere.
  Relationships with no R-DID are unaffected — `remote_did` equals
  `remote_p_did` there, and a test pins that.

The `#key-1` verification-method convention that `generate_did_peer_from_secrets`
imposes was already load-bearing in two places and spelled out longhand in
both. It is now named — `relationship_signing_vm_id` — with the secret lookup
beside it in `openvtc-core`, so the TUI crate does not reach into the secrets
resolver directly.

Docs: the note deferring "full pairwise operation, including the VRC issuer
field" to upstream work is replaced by what actually happens, including why the
issuer field mattered more than the handshake routing does. The example
credential showed a `did:webvh` issuer; it now shows the `did:peer` one it
would really carry.

What #241 leaves open after this is the handshake routing itself, which is
intrinsic — the mediator must route request/accept/finalise before a pairwise
channel exists — and worth closing as accepted rather than outstanding.

BREAKING CHANGE: a VRC issued by a peer running an older build carries their
persona DID and is now refused, and one issued by this build is refused by
older peers. Nothing is published yet, so no compatibility window is provided.

Refs #241, OpenVTC/verifiable-trust-infrastructure#1054

Signed-off-by: Glenn Gore <glenn.g@affinidi.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant