Skip to content

Resolve did:key verification locally#915

Open
dahlia wants to merge 2 commits into
fedify-dev:mainfrom
dahlia:feat/fep-ef61/did-key-oip
Open

Resolve did:key verification locally#915
dahlia wants to merge 2 commits into
fedify-dev:mainfrom
dahlia:feat/fep-ef61/did-key-oip

Conversation

@dahlia

@dahlia dahlia commented Jul 4, 2026

Copy link
Copy Markdown
Member

Object Integrity Proofs on portable ActivityPub objects cannot rely on the usual remote key lookup path. In the FEP-ef61 shape, the object can be identified under an ap://did:key:... authority, and the proof can point at a did:key:z...#z... verification method. Treating that verification method as a JSON-LD document would make the proof depend on a fetch that has no useful remote document to fetch.

This PR handles that case inside key resolution instead. packages/fedify/src/sig/key.ts now recognizes supported did:key DID URLs before the document loader path, parses only the Ed25519 did:key:z...#z... form, and turns it into a Multikey directly. Unsupported DID keys and malformed verification methods fail locally, without poisoning the key cache and without falling back to a remote lookup. That keeps the implementation narrow: it is not a general DID resolver, and it does not add policy checks about whether a portable object ID is authorized by a proof. It only supplies the public key needed by proof verification.

The parsing and key conversion live in packages/vocab-runtime/src/key.ts so the low-level rules are shared and testable. The new helpers are:

  • exportDidKey()
  • importDidKey()
  • parseDidKeyVerificationMethod()

They deliberately accept Ed25519 did:key values only. RSA multicodec keys, non-base58btc multibase values, malformed multicodec prefixes, short Ed25519 keys, bare DIDs where a verification method is required, and mismatched fragments are rejected before a CryptoKey is returned.

The proof tests exercise the FEP-ef61/FEP-8b32 path with a portable ap:// object ID and a did:key verification method. The document loader throws in that test, so a passing test proves that verification used the local key resolution path. The same test also covers tampering, fragment mismatch, and an unsupported RSA did:key proof that must not fall back to document loading.

I also updated the Object Integrity Proof documentation in docs/manual/send.md and the key lookup metric description in docs/manual/opentelemetry.md. The metric result remains fetched for successful cold lookups, but the docs now spell out that a cold lookup may be local key resolution rather than an HTTP document fetch.

Fixes #827.

Resolve Ed25519 did:key verification methods locally so Object
Integrity Proof verification can work for portable ActivityPub objects
without fetching the verification method as a remote JSON-LD document.

Add vocab-runtime helpers for exporting and importing Ed25519 did:key
DIDs, parsing did:key verification method DID URLs, and rejecting
unsupported or malformed DID keys before they reach remote lookup paths.

Document the new proof verification behavior and update the changelog for
PR 915.

Fixes fedify-dev#827
fedify-dev#915

Assisted-by: Codex:gpt-5.5
@dahlia dahlia added this to the Fedify 2.4 milestone Jul 4, 2026
@dahlia dahlia self-assigned this Jul 4, 2026
@dahlia dahlia requested review from 2chanhaeng and sij411 as code owners July 4, 2026 03:35
@dahlia dahlia added component/vocab Activity Vocabulary related component/signatures OIP or HTTP/LD Signatures related labels Jul 4, 2026
@dahlia dahlia added the activitypub/compliance Specification compliance label Jul 4, 2026
@netlify

netlify Bot commented Jul 4, 2026

Copy link
Copy Markdown

Deploy Preview for fedify-json-schema canceled.

Name Link
🔨 Latest commit fbe007a
🔍 Latest deploy log https://app.netlify.com/projects/fedify-json-schema/deploys/6a4885104d57460008d5ce4a

@dahlia

dahlia commented Jul 4, 2026

Copy link
Copy Markdown
Member Author

@codex review

@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 158245cc-776e-4e8d-8bba-cc7bf26e54f1

📥 Commits

Reviewing files that changed from the base of the PR and between 6aba22a and fbe007a.

📒 Files selected for processing (1)
  • packages/fedify/src/sig/key.ts

📝 Walkthrough

Walkthrough

Adds did:key support for Ed25519 keys: new runtime helpers, local key resolution in fedify’s key-fetch path, proof verification coverage, and related documentation/changelog updates.

Changes

did:key support

Layer / File(s) Summary
did:key runtime helpers
packages/vocab-runtime/src/key.ts, packages/vocab-runtime/src/mod.ts, packages/vocab-runtime/src/key.test.ts
Adds DidKeyVerificationMethod and exportDidKey/importDidKey/parseDidKeyVerificationMethod, re-exports them, and tests round-trips plus malformed inputs.
fedify key-fetch integration
packages/fedify/src/sig/key.ts, packages/fedify/src/sig/key.test.ts
Adds local did:key resolution before remote fetch, preserves cache handling and lookup outcomes, and extends tests for valid, invalid, and metric-bearing cases.
Object Integrity Proof verification
packages/fedify/src/sig/proof.test.ts
Adds a verifyProof() test that resolves did:key verification methods locally and checks tampered and mismatched proof cases.
Documentation and changelog
CHANGES.md, docs/manual/opentelemetry.md, docs/manual/send.md
Updates release notes and manuals for local did:key resolution, metric taxonomies, and FEP-ef61 proof verification text.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant fetchKeyWithResult as key.ts
  participant resolveDidKey as key.ts
  participant parseDidKeyVerificationMethod as vocab-runtime
  participant keyCache

  Caller->>fetchKeyWithResult: fetchKey(did:key URL, Multikey)
  fetchKeyWithResult->>resolveDidKey: resolveDidKey(url, cls)
  resolveDidKey->>keyCache: check cache
  alt cache miss
    resolveDidKey->>parseDidKeyVerificationMethod: parseDidKeyVerificationMethod(url)
    parseDidKeyVerificationMethod-->>resolveDidKey: DidKeyVerificationMethod
    resolveDidKey->>keyCache: cache Multikey
  end
  resolveDidKey-->>fetchKeyWithResult: Multikey / invalid result
  fetchKeyWithResult-->>Caller: return early without documentLoader
Loading

Possibly related issues

Possibly related PRs

  • fedify-dev/fedify#771: Changes the same key-lookup/metric path in packages/fedify/src/sig/key.ts, which this PR extends for did:key resolution.

Suggested labels: type/bug

Suggested reviewers: sij411, 2chanhaeng

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: local resolution of did:key verification.
Description check ✅ Passed The description is detailed and directly matches the implemented did:key verification work.
Linked Issues check ✅ Passed The changes implement Ed25519 did:key helpers, local proof verification, and tests aligned with #827.
Out of Scope Changes check ✅ Passed The docs and changelog updates are supporting changes for the same did:key verification work.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces local resolution for Ed25519 did:key verification methods, allowing Object Integrity Proofs (FEP-8b32) and portable objects (FEP-ef61) to be verified without fetching remote JSON-LD documents. It adds several helper functions (exportDidKey, importDidKey, and parseDidKeyVerificationMethod) to @fedify/vocab-runtime, integrates them into fetchKey and verifyProof, and updates OpenTelemetry metrics and documentation accordingly. The review feedback highlights two important improvements in packages/fedify/src/sig/key.ts: first, handling cached negative entries (cachedKey === null) in resolveDidKey to avoid redundant parsing, and second, correcting the metric outcome logic so that negative cache hits are properly recorded as "hit" instead of "invalid".

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread packages/fedify/src/sig/key.ts Outdated
Comment thread packages/fedify/src/sig/key.ts
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 👍

Reviewed commit: 6aba22a44f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/fedify/src/sig/key.ts (1)

396-419: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Cache-hit check duplicated between getCachedFetchKey and resolveDidKey.

Both functions independently re-implement cachedKey instanceof cls && cachedKey.publicKey != null, but resolveDidKey doesn't handle the explicit cachedKey === null ("entry cached but unavailable") branch that getCachedFetchKey does. This divergence is easy to miss on future edits to either function.

♻️ Suggested extraction
+function checkCachedKeyHit<T extends CryptographicKey | Multikey>(
+  cachedKey: CryptographicKey | Multikey | null | undefined,
+  cls: FetchableKeyClass<T>,
+): (T & { publicKey: CryptoKey }) | undefined {
+  if (cachedKey instanceof cls && cachedKey.publicKey != null) {
+    return cachedKey as T & { publicKey: CryptoKey };
+  }
+  return undefined;
+}

Then reuse checkCachedKeyHit in both getCachedFetchKey and resolveDidKey.

Also applies to: 431-483

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/fedify/src/sig/key.ts` around lines 396 - 419, The cache-hit logic
is duplicated between getCachedFetchKey and resolveDidKey and the null-cache
case is handled only in getCachedFetchKey. Extract the shared check into a
helper such as checkCachedKeyHit that encapsulates the instanceof cls plus
publicKey presence and the explicit cachedKey === null unavailable branch, then
have both getCachedFetchKey and resolveDidKey call it so the behavior stays
consistent in one place.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@packages/fedify/src/sig/key.ts`:
- Around line 396-419: The cache-hit logic is duplicated between
getCachedFetchKey and resolveDidKey and the null-cache case is handled only in
getCachedFetchKey. Extract the shared check into a helper such as
checkCachedKeyHit that encapsulates the instanceof cls plus publicKey presence
and the explicit cachedKey === null unavailable branch, then have both
getCachedFetchKey and resolveDidKey call it so the behavior stays consistent in
one place.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: b961de55-cf73-48d4-8691-886369e3cda1

📥 Commits

Reviewing files that changed from the base of the PR and between 0896bc5 and 6aba22a.

📒 Files selected for processing (9)
  • CHANGES.md
  • docs/manual/opentelemetry.md
  • docs/manual/send.md
  • packages/fedify/src/sig/key.test.ts
  • packages/fedify/src/sig/key.ts
  • packages/fedify/src/sig/proof.test.ts
  • packages/vocab-runtime/src/key.test.ts
  • packages/vocab-runtime/src/key.ts
  • packages/vocab-runtime/src/mod.ts

@codecov

codecov Bot commented Jul 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ All tests successful. No failed tests found.

Files with missing lines Coverage Δ
packages/fedify/src/sig/key.ts 89.50% <100.00%> (+1.38%) ⬆️
packages/vocab-runtime/src/key.ts 91.62% <100.00%> (+4.01%) ⬆️
packages/vocab-runtime/src/mod.ts 100.00% <100.00%> (ø)

... and 4 files with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Keep positive key cache hits in one helper so regular remote key lookups
and local did:key resolution use the same class and public key checks.
The did:key path still ignores stale negative cache entries because it can
resolve supported keys locally and replace the old unavailable entry.

fedify-dev#915 (review)

Assisted-by: Codex:gpt-5.5
@dahlia

dahlia commented Jul 4, 2026

Copy link
Copy Markdown
Member Author

/gemini review

@dahlia

dahlia commented Jul 4, 2026

Copy link
Copy Markdown
Member Author

@codex review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces local resolution for Ed25519 did:key verification methods, allowing verifyProof() to verify Object Integrity Proofs without fetching remote JSON-LD documents. It adds helper functions (exportDidKey, importDidKey, and parseDidKeyVerificationMethod) to @fedify/vocab-runtime, updates OpenTelemetry metrics to handle hostless key identifiers, and includes comprehensive tests. The reviewer's feedback points out an important improvement in resolveDidKey to correctly respect negative cache entries when a key is cached as unavailable, preventing redundant resolution attempts.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread packages/fedify/src/sig/key.ts
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🚀

Reviewed commit: fbe007a548

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

activitypub/compliance Specification compliance component/signatures OIP or HTTP/LD Signatures related component/vocab Activity Vocabulary related

Development

Successfully merging this pull request may close these issues.

Resolve did:key verification methods for Object Integrity Proofs

1 participant