Skip to content

feat(rooms-wasm)!: a browser can verify a did:webvh room's invitation - #1382

Open
stormer78 wants to merge 1 commit into
mainfrom
feat/wasm-invitation-issuer-key
Open

feat(rooms-wasm)!: a browser can verify a did:webvh room's invitation#1382
stormer78 wants to merge 1 commit into
mainfrom
feat/wasm-invitation-issuer-key

Conversation

@stormer78

Copy link
Copy Markdown
Contributor

The invitation gate resolved did:key and did:peer and refused everything else. So a browser member could be admitted to the rooms this crate's own demo mints, and to nothing else.

A production room is a did:webvh. So is a VTC-hosted one. Resolving one means fetching a log over HTTPS and verifying its history — that is I/O, and this crate compiles to a target where I/O belongs to the host page.

The caller may now supply the issuer's key

That moves where trust sits, so the module states it precisely rather than leaving a reader to infer it:

  • did:key and did:peer derive the key from the identifier, and a supplied key is ignored. Preferring the caller's would let them talk this module out of the one check it can make entirely on its own. Asserted, not merely described — the test passes a wrong key to a did:key room and expects it to verify anyway.
  • Anything else is the caller's resolution, and the resolution is the trust. The proof must still name the issuer, and must still verify under the key supplied, so a wrong key fails. What this module cannot do is tell a correctly-resolved key from a convincingly-wrong one. That is the resolver's job, and it is exactly why this is a parameter rather than an HTTP client the crate could not use anyway.

Breaking

verify, mint_key_package, RoomMember::join and the two _js wrappers take issuer_key: Option<Vec<u8>>. None is precisely the previous behaviour, so every existing call site is a one-word change.

A note from writing the test

The first cut signed with a did:key secret and rewrote verificationMethod to point at the room afterwards. That fails — an eddsa-jcs-2022 proof covers its own proof options, verificationMethod included — which is the proof doing its job. The fixture gained a helper that signs under an arbitrary verification method, so the credential is genuine rather than edited.

Testing

11 tests pass; clippy clean; builds for wasm32-unknown-unknown under the wasm-release profile.

The invitation gate resolved `did:key` and `did:peer` and refused
everything else, which meant a browser member could be admitted to the
rooms this crate's demo mints and to nothing else. **A production room is
a `did:webvh`** — so is a VTC-hosted one — and resolving one means
fetching a log over HTTPS and verifying its history. That is I/O, and
this crate compiles to a target where I/O belongs to the host page.

So the caller may now supply the issuer's key. The split moves where
trust sits, and the module says so precisely rather than leaving a reader
to work it out:

- `did:key` and `did:peer` derive the key **from the identifier**, and a
  supplied one is ignored. Preferring the caller's would let them talk
  this module out of the one check it can make entirely alone — asserted,
  not just described.
- Anything else is the caller's resolution, and the resolution is the
  trust. The proof must still name the issuer and still verify under the
  key given, so a wrong key fails; what this module cannot do is tell a
  correctly-resolved key from a convincingly-wrong one. That is the
  resolver's job, and it is why this is a parameter rather than an HTTP
  client this crate could not use.

Breaking: `verify`, `mint_key_package`, `RoomMember::join` and the two
`_js` wrappers take `issuer_key: Option<Vec<u8>>`. `None` is exactly the
old behaviour.

Writing the test found something worth keeping: the first cut signed with
a `did:key` secret and rewrote `verificationMethod` afterwards, which
fails — an `eddsa-jcs-2022` proof covers its own proof options. The
fixture signs under the room's own method instead.
@stormer78
stormer78 requested a review from a team as a code owner September 9, 2026 20:52
stormer78 added a commit to stormer78/vti-data-room-demo that referenced this pull request Sep 9, 2026
`advertised()` opened with `if (!did.startsWith("did:peer:")) return null`,
which meant the site could dial the rooms this sample mints and nothing
else. A production room is a `did:webvh`. So is a VTC-hosted one. So the
front door only opened onto the demo.

It now goes through the stack's own resolver — `did:key` and `did:peer`
still by computation, `did:webvh` by fetching and verifying its log,
which is why resolution became async and why that rippled.

That second half needs a change in wasm, and this carries the JS side of
it (OpenVTC/verifiable-trust-infrastructure#1382). The invitation gate
cannot resolve a `did:webvh`: it means HTTPS and log verification, which
is I/O, and wasm has none. So this page resolves the room's signing key
and passes it in — and passes `null` for `did:key` and `did:peer`, where
the gate derives the key from the identifier and ignores anything offered.
Nothing this page believes about those rooms can affect the verdict, which
is the property worth keeping and the reason `issuerKey` returns null
rather than being helpful.

Verified against the live mediator: a `did:webvh` resolves to its DIDComm
service and yields a 32-byte issuer key, a `did:key` resolves to no
service, and joining a `did:peer` room still works unchanged.

One bug of my own on the way, and the comment stays where it bit: the
resolved key was called `key`, which shadows this file's `key(suffix)`
storage helper. A working join failed with "key is not a function" from a
line well below the declaration.
@affinidi-appsecurity-bot

Copy link
Copy Markdown

🛡️ AI Agentic Security Code Review — all clear. We checked this change and found nothing to report. Keep shipping secure code!

Note: for major, breaking, or feature-introducing changes, you can always request an in-depth review from the security team.

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.

2 participants