@fedify/fedify 2.0.36-dev.2530 → 2.0.36-dev.2598

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/{builder-BBdPdHYx.mjs → builder-B5hI5Xsn.mjs} +3 -3
  2. package/dist/compat/transformers.test.mjs +1 -1
  3. package/dist/{deno-CtG6TwTB.mjs → deno--OYa_lxj.mjs} +1 -1
  4. package/dist/{docloader-BxW_P_Rq.mjs → docloader-BXR2f83H.mjs} +2 -2
  5. package/dist/federation/builder.test.mjs +1 -1
  6. package/dist/federation/handler.test.mjs +8 -8
  7. package/dist/federation/idempotency.test.mjs +2 -2
  8. package/dist/federation/inbox.test.mjs +1 -1
  9. package/dist/federation/middleware.test.mjs +381 -8
  10. package/dist/federation/mod.cjs +1 -1
  11. package/dist/federation/mod.js +1 -1
  12. package/dist/federation/send.test.mjs +3 -3
  13. package/dist/federation/temporal.test.mjs +2 -1
  14. package/dist/federation/webfinger.test.mjs +1 -1
  15. package/dist/{http-CUOxcDgn.mjs → http-C3d2DXwF.mjs} +2 -2
  16. package/dist/{http-DMRapH1d.cjs → http-CO_IVq7x.cjs} +1 -1
  17. package/dist/{http-YAfhvSad.js → http-CfyVeGgF.js} +1 -1
  18. package/dist/{inbox-DtThTl1D.mjs → inbox-huUE3IP8.mjs} +1 -1
  19. package/dist/{key-QamJxksu.mjs → key-D5e9c_yV.mjs} +1 -1
  20. package/dist/{kv-cache-Di_SbH_3.cjs → kv-cache-BMlo_kyv.cjs} +1 -1
  21. package/dist/{kv-cache-C0MyXzft.js → kv-cache-Zw0soD-7.js} +1 -1
  22. package/dist/{ld-B0eNy8WK.mjs → ld-9qfz9rP_.mjs} +99 -10
  23. package/dist/{middleware-D3omYPtg.cjs → middleware-Ad6kTSBf.cjs} +1 -1
  24. package/dist/{middleware-D1zogq17.js → middleware-BKs_a9P1.js} +111 -15
  25. package/dist/{middleware-II_yrP7N.mjs → middleware-BhYpLGA_.mjs} +119 -23
  26. package/dist/{middleware-BKECCXIs.mjs → middleware-CT1Lbohd.mjs} +1 -1
  27. package/dist/{middleware-CUFw4tT0.cjs → middleware-CXoG7ZAW.cjs} +112 -16
  28. package/dist/{mod-DVPS4yam.d.cts → mod-BA6vqC2S.d.cts} +19 -7
  29. package/dist/{mod-BS-jg3Wx.d.ts → mod-CL9Ep0uo.d.ts} +19 -7
  30. package/dist/mod.cjs +4 -4
  31. package/dist/mod.d.cts +1 -1
  32. package/dist/mod.d.ts +1 -1
  33. package/dist/mod.js +4 -4
  34. package/dist/nodeinfo/handler.test.mjs +1 -1
  35. package/dist/{owner-lqq4KXYV.mjs → owner-R4ffU9Ht.mjs} +2 -2
  36. package/dist/{proof-CqOINd75.cjs → proof-BrreJTRF.cjs} +131 -18
  37. package/dist/{proof-DMYwiD7d.js → proof-CE8nwTF6.js} +132 -19
  38. package/dist/{proof-Bf8rYD7r.mjs → proof-CiBs9Q4l.mjs} +36 -12
  39. package/dist/{send-1tVKmxvT.mjs → send-DpvZgVSA.mjs} +2 -2
  40. package/dist/sig/http.test.mjs +2 -2
  41. package/dist/sig/key.test.mjs +2 -2
  42. package/dist/sig/ld.test.mjs +198 -4
  43. package/dist/sig/mod.cjs +2 -2
  44. package/dist/sig/mod.d.cts +1 -1
  45. package/dist/sig/mod.d.ts +1 -1
  46. package/dist/sig/mod.js +2 -2
  47. package/dist/sig/owner.test.mjs +1 -1
  48. package/dist/sig/proof.test.mjs +71 -2
  49. package/dist/{temporal-D6JQXNOJ.mjs → temporal-Dd-J410Z.mjs} +1 -1
  50. package/dist/utils/docloader.test.mjs +2 -2
  51. package/dist/utils/mod.cjs +1 -1
  52. package/dist/utils/mod.js +1 -1
  53. package/package.json +5 -5
@@ -1,7 +1,7 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-CtG6TwTB.mjs";
4
+ import { n as version, t as name } from "./deno--OYa_lxj.mjs";
5
5
  import { CryptographicKey, Object as Object$1, isActor } from "@fedify/vocab";
6
6
  import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
7
7
  import { FetchError, UrlError, getDocumentLoader } from "@fedify/vocab-runtime";
@@ -1,7 +1,7 @@
1
1
  const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
3
  require("./chunk-DDcVe30Y.cjs");
4
- const require_http = require("./http-DMRapH1d.cjs");
4
+ const require_http = require("./http-CO_IVq7x.cjs");
5
5
  let _logtape_logtape = require("@logtape/logtape");
6
6
  let es_toolkit = require("es-toolkit");
7
7
  let _fedify_vocab_runtime = require("@fedify/vocab-runtime");
@@ -1,6 +1,6 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
- import { l as validateCryptoKey, t as doubleKnockScoped } from "./http-YAfhvSad.js";
3
+ import { l as validateCryptoKey, t as doubleKnockScoped } from "./http-CfyVeGgF.js";
4
4
  import { getLogger } from "@logtape/logtape";
5
5
  import { curry } from "es-toolkit";
6
6
  import { FetchError, UrlError, createActivityPubRequest, getRemoteDocument, logRequest, preloadedContexts } from "@fedify/vocab-runtime";
@@ -1,11 +1,11 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-CtG6TwTB.mjs";
5
- import { o as validateCryptoKey, r as fetchKey } from "./key-QamJxksu.mjs";
4
+ import { n as version, t as name } from "./deno--OYa_lxj.mjs";
5
+ import { o as validateCryptoKey, r as fetchKey } from "./key-D5e9c_yV.mjs";
6
6
  import { Activity, CryptographicKey, Object as Object$1, getTypeId } from "@fedify/vocab";
7
7
  import { SpanStatusCode, trace } from "@opentelemetry/api";
8
- import { getDocumentLoader } from "@fedify/vocab-runtime";
8
+ import { getDocumentLoader, preloadedContexts } from "@fedify/vocab-runtime";
9
9
  import { getLogger } from "@logtape/logtape";
10
10
  import { decodeBase64, encodeBase64 } from "byte-encodings/base64";
11
11
  import { encodeHex } from "byte-encodings/hex";
@@ -298,24 +298,110 @@ function assertSafeJsonLd(jsonLd) {
298
298
  }
299
299
  }
300
300
  }
301
+ const signatureTermDefinition = { "signature": "https://w3id.org/security#signature" };
302
+ const signatureDefiningContexts = new Set(["https://w3id.org/identity/v1", "https://w3id.org/security/v1"]);
303
+ const signatureFreeContexts = getSignatureFreeContexts();
304
+ function getSignatureFreeContexts() {
305
+ const urls = /* @__PURE__ */ new Set();
306
+ for (const [url, document] of globalThis.Object.entries(preloadedContexts)) {
307
+ if (typeof document !== "object" || document == null) continue;
308
+ const context = document["@context"];
309
+ if (typeof context !== "object" || context == null || Array.isArray(context)) continue;
310
+ const record = context;
311
+ if (!globalThis.Object.prototype.hasOwnProperty.call(record, "signature")) urls.add(url);
312
+ }
313
+ return urls;
314
+ }
315
+ function getContextEntryEffect(entry) {
316
+ if (entry == null) return "resets";
317
+ if (typeof entry === "string") {
318
+ if (signatureDefiningContexts.has(entry)) return "defines";
319
+ if (signatureFreeContexts.has(entry)) return "absent";
320
+ return "unknown";
321
+ }
322
+ if (typeof entry !== "object") return "absent";
323
+ const record = entry;
324
+ if (globalThis.Object.prototype.hasOwnProperty.call(record, "signature")) {
325
+ const definition = record["signature"];
326
+ const contextProtected = record["@protected"] === true;
327
+ const imported = globalThis.Object.prototype.hasOwnProperty.call(record, "@import");
328
+ if (definition == null) {
329
+ if (contextProtected) return "defines";
330
+ return imported ? "unknown" : "resets";
331
+ }
332
+ if (typeof definition === "object" && !Array.isArray(definition)) {
333
+ const term = definition;
334
+ const protectedTerm = contextProtected || term["@protected"] === true;
335
+ if (globalThis.Object.prototype.hasOwnProperty.call(term, "@id") && term["@id"] == null) {
336
+ if (protectedTerm) return "defines";
337
+ return imported ? "unknown" : "resets";
338
+ }
339
+ }
340
+ return "defines";
341
+ }
342
+ if (globalThis.Object.prototype.hasOwnProperty.call(record, "@import")) return "unknown";
343
+ return "absent";
344
+ }
345
+ function getSignatureTermState(context) {
346
+ if (context == null) return "absent";
347
+ if (!Array.isArray(context)) {
348
+ const effect = getContextEntryEffect(context);
349
+ return effect === "defines" ? "defined" : effect === "unknown" ? "unknown" : "absent";
350
+ }
351
+ let state = "absent";
352
+ for (const entry of context) {
353
+ const effect = getContextEntryEffect(entry);
354
+ if (effect === "defines") state = "defined";
355
+ else if (effect === "resets") state = "absent";
356
+ else if (effect === "unknown" && state !== "defined") state = "unknown";
357
+ }
358
+ return state;
359
+ }
360
+ function prepareSignatureDocument(jsonLd) {
361
+ if (typeof jsonLd !== "object" || jsonLd == null || Array.isArray(jsonLd)) return jsonLd;
362
+ const document = jsonLd;
363
+ if (document["proof"] != null) return { ...document };
364
+ const context = document["@context"];
365
+ if (getSignatureTermState(context) !== "absent") return { ...document };
366
+ const definition = { ...signatureTermDefinition };
367
+ return {
368
+ ...document,
369
+ "@context": context == null ? [definition] : Array.isArray(context) ? [...context, definition] : [context, definition]
370
+ };
371
+ }
301
372
  /**
302
373
  * Attaches a LD signature to the given JSON-LD document.
374
+ *
375
+ * When the document's context does not already define the `signature`
376
+ * property, the returned document adds a minimal inline context for it.
377
+ * Without that definition, expanding the attached signature would produce a
378
+ * blank node predicate, which is not valid RDF. Documents that already carry
379
+ * a `proof` are left unchanged, since a new context definition would invalidate
380
+ * the proof. Documents whose context includes an unrecognized remote context
381
+ * are also left unchanged, since that context may already define `signature`.
303
382
  * @param jsonLd The JSON-LD document to attach the signature to. It is not
304
383
  * modified.
305
384
  * @param signature The signature to attach.
306
385
  * @returns The JSON-LD document with the attached signature.
307
386
  * @throws {TypeError} If the input document is not a valid JSON-LD document.
387
+ * Top-level arrays are rejected, since a Linked Data
388
+ * Signature is attached to a single node.
308
389
  * @since 1.0.0
309
390
  */
310
391
  function attachSignature(jsonLd, signature) {
311
- if (typeof jsonLd !== "object" || jsonLd == null) throw new TypeError("Failed to attach signature; invalid JSON-LD document.");
312
- return {
313
- ...jsonLd,
314
- signature
315
- };
392
+ if (typeof jsonLd !== "object" || jsonLd == null || Array.isArray(jsonLd)) throw new TypeError("Failed to attach signature; invalid JSON-LD document.");
393
+ const output = prepareSignatureDocument(jsonLd);
394
+ output.signature = signature;
395
+ return output;
316
396
  }
317
397
  /**
318
398
  * Creates a LD signature for the given JSON-LD document.
399
+ *
400
+ * If the document's context does not define the `signature` property, the
401
+ * signature is computed over the document with a minimal inline definition
402
+ * added. Use {@link attachSignature} to attach the signature to that exact
403
+ * document. Documents that already carry a `proof`, or whose context includes
404
+ * an unrecognized remote context, are hashed unchanged.
319
405
  * @param jsonLd The JSON-LD document to sign.
320
406
  * @param privateKey The private key to sign the document.
321
407
  * @param keyId The ID of the public key that corresponds to the private key.
@@ -329,14 +415,17 @@ async function createSignature(jsonLd, privateKey, keyId, { contextLoader, creat
329
415
  validateCryptoKey(privateKey, "private");
330
416
  if (privateKey.algorithm.name !== "RSASSA-PKCS1-v1_5") throw new TypeError("Unsupported algorithm: " + privateKey.algorithm.name);
331
417
  const options = {
332
- "@context": "https://w3id.org/identity/v1",
333
418
  creator: keyId.href,
334
419
  created: created?.toString() ?? (/* @__PURE__ */ new Date()).toISOString()
335
420
  };
336
- const message = await hashJsonLd(options, contextLoader) + await hashJsonLd(jsonLd, contextLoader);
421
+ const message = await hashJsonLd({
422
+ "@context": "https://w3id.org/identity/v1",
423
+ ...options
424
+ }, contextLoader) + await hashJsonLd(prepareSignatureDocument(jsonLd), contextLoader);
337
425
  const messageBytes = new TextEncoder().encode(message);
338
426
  const signature = await crypto.subtle.sign("RSASSA-PKCS1-v1_5", privateKey, messageBytes);
339
427
  return {
428
+ "@context": "https://w3id.org/security/v1",
340
429
  ...options,
341
430
  type: "RsaSignature2017",
342
431
  signatureValue: encodeBase64(signature)
@@ -1,4 +1,4 @@
1
1
  const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
- const require_middleware = require("./middleware-CUFw4tT0.cjs");
3
+ const require_middleware = require("./middleware-CXoG7ZAW.cjs");
4
4
  exports.FederationImpl = require_middleware.FederationImpl;
@@ -2,10 +2,10 @@ import { Temporal } from "@js-temporal/polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
3
  import { t as __exportAll } from "./chunk-CRNNMoPX.js";
4
4
  import { r as getDefaultActivityTransformers } from "./transformers-BGMIq1cs.js";
5
- import { c as importJwk, d as name, f as version, i as exportJwk, l as validateCryptoKey, r as verifyRequest, t as doubleKnockScoped } from "./http-YAfhvSad.js";
5
+ import { c as importJwk, d as name, f as version, i as exportJwk, l as validateCryptoKey, r as verifyRequest, t as doubleKnockScoped } from "./http-CfyVeGgF.js";
6
6
  import { a as MAX_BODY_SIZE, i as BodyTooLargeError, n as getNodeInfo, o as readBoundedText, t as nodeInfoToJson } from "./types-B1_pRFE_.js";
7
- import { _ as signJsonLd, a as doesActorOwnKey, c as assertSafeJsonLd, f as detachSignature, g as isInvalidUrlTypeError, h as isClearlyMalformedContextReference, m as hasSignature, n as signObject, o as getKeyOwner, p as getNormalizationContextLoader, r as verifyObject, s as InvalidContextReferenceError, u as compactJsonLd, v as verifyCompactJsonLd, x as wrapContextLoaderForJsonLd } from "./proof-DMYwiD7d.js";
8
- import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-C0MyXzft.js";
7
+ import { _ as signJsonLd, a as doesActorOwnKey, c as assertSafeJsonLd, f as detachSignature, g as isInvalidUrlTypeError, h as isClearlyMalformedContextReference, m as hasSignature, n as signObject, o as getKeyOwner, p as getNormalizationContextLoader, r as verifyObject, s as InvalidContextReferenceError, u as compactJsonLd, v as verifyCompactJsonLd, x as wrapContextLoaderForJsonLd } from "./proof-CE8nwTF6.js";
8
+ import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-Zw0soD-7.js";
9
9
  import { getLogger, withContext } from "@logtape/logtape";
10
10
  import { Activity, Collection, CollectionPage, CryptographicKey, Link, Multikey, Object as Object$1, OrderedCollection, OrderedCollectionPage, getTypeId, lookupObject, traverseCollection } from "@fedify/vocab";
11
11
  import { SpanKind, SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
@@ -3002,7 +3002,8 @@ var FederationImpl = class extends FederationBuilderImpl {
3002
3002
  await this.sendActivity(keys, message.inboxes, activity, {
3003
3003
  collectionSync: message.collectionSync,
3004
3004
  orderingKey: message.orderingKey,
3005
- context
3005
+ context,
3006
+ activityJsonLd: hasTopLevelProof(message.activity) ? message.activity : void 0
3006
3007
  });
3007
3008
  }
3008
3009
  async #listenOutboxMessage(_, message, span) {
@@ -3409,7 +3410,7 @@ var FederationImpl = class extends FederationBuilderImpl {
3409
3410
  proofCreated = true;
3410
3411
  }
3411
3412
  }
3412
- let jsonLd = await activity.toJsonLd({
3413
+ let jsonLd = options.activityJsonLd != null ? detachSignature(options.activityJsonLd) : await activity.toJsonLd({
3413
3414
  format: "compact",
3414
3415
  contextLoader
3415
3416
  });
@@ -4126,7 +4127,17 @@ var ContextImpl = class ContextImpl {
4126
4127
  allowPrivateAddress: this.federation.allowPrivateAddress
4127
4128
  });
4128
4129
  }
4129
- sendActivity(sender, recipients, activity, options = {}) {
4130
+ async sendActivity(sender, recipients, activity, options = {}) {
4131
+ let activityJsonLd = void 0;
4132
+ let hasProof = activity.proofIds.length > 0;
4133
+ if (!hasProof) for await (const _ of activity.getProofs({ contextLoader: this.contextLoader })) {
4134
+ hasProof = true;
4135
+ break;
4136
+ }
4137
+ if (hasProof) {
4138
+ activityJsonLd = await activity.toJsonLd({ contextLoader: this.contextLoader });
4139
+ if (!hasTopLevelProof(activityJsonLd)) activityJsonLd = void 0;
4140
+ }
4130
4141
  return this.tracerProvider.getTracer(name, version).startActiveSpan(this.federation.outboxQueue == null || options.immediate ? "activitypub.outbox" : "activitypub.fanout", {
4131
4142
  kind: this.federation.outboxQueue == null || options.immediate ? SpanKind.CLIENT : SpanKind.PRODUCER,
4132
4143
  attributes: {
@@ -4139,7 +4150,7 @@ var ContextImpl = class ContextImpl {
4139
4150
  }, async (span) => {
4140
4151
  try {
4141
4152
  if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
4142
- await this.sendActivityInternal(sender, recipients, activity, options, span);
4153
+ await this.sendActivityInternal(sender, recipients, activity, options, span, activityJsonLd);
4143
4154
  } catch (e) {
4144
4155
  span.setStatus({
4145
4156
  code: SpanStatusCode.ERROR,
@@ -4151,7 +4162,7 @@ var ContextImpl = class ContextImpl {
4151
4162
  }
4152
4163
  });
4153
4164
  }
4154
- async sendActivityInternal(sender, recipients, activity, options, span) {
4165
+ async sendActivityInternal(sender, recipients, activity, options, span, activityJsonLd) {
4155
4166
  const logger = getLogger([
4156
4167
  "fedify",
4157
4168
  "federation",
@@ -4198,14 +4209,23 @@ var ContextImpl = class ContextImpl {
4198
4209
  if (!(error instanceof RouterError)) throw error;
4199
4210
  }
4200
4211
  } else expandedRecipients = [recipients];
4201
- const opts = {
4202
- context: this,
4203
- orderingKey: options.orderingKey,
4204
- collectionSync,
4205
- immediate: options.immediate
4206
- };
4207
4212
  span.setAttribute("activitypub.inboxes", expandedRecipients.length);
4208
4213
  for (const activityTransformer of this.federation.activityTransformers) activity = activityTransformer(activity, this);
4214
+ let contextMatches = false;
4215
+ if (activityJsonLd != null) {
4216
+ const capturedJsonLd = activityJsonLd;
4217
+ const transformedJsonLd = await activity.toJsonLd({
4218
+ format: "compact",
4219
+ contextLoader: this.contextLoader
4220
+ });
4221
+ const contentChanged = getContentFields(transformedJsonLd) !== getContentFields(capturedJsonLd);
4222
+ contextMatches = getContextFields(transformedJsonLd) === getContextFields(capturedJsonLd);
4223
+ const proofsChanged = getProofFields(transformedJsonLd) !== getProofFields(capturedJsonLd);
4224
+ if (contentChanged || !contextMatches && proofsChanged) {
4225
+ activity = activity.clone({ proofs: await getUncapturedProofs(activity, capturedJsonLd, this.contextLoader) });
4226
+ activityJsonLd = void 0;
4227
+ } else if (proofsChanged) activityJsonLd = void 0;
4228
+ }
4209
4229
  span?.setAttribute("activitypub.activity.id", activity?.id?.href ?? "");
4210
4230
  if (activity.actorId == null) {
4211
4231
  logger.error("Activity {activityId} to send does not have an actor.", {
@@ -4216,14 +4236,27 @@ var ContextImpl = class ContextImpl {
4216
4236
  }
4217
4237
  if (actorKeyPairs != null) {
4218
4238
  const contextLoader = this.contextLoader;
4239
+ if (actorKeyPairs.some((kp) => kp.privateKey.algorithm.name === "Ed25519" && kp.multikey.id != null)) {
4240
+ if (activityJsonLd != null && !contextMatches) activity = activity.clone({ proofs: [] });
4241
+ }
4242
+ let proofAdded = false;
4219
4243
  for (const kp of actorKeyPairs) {
4220
4244
  if (kp.privateKey.algorithm.name !== "Ed25519" || kp.multikey.id == null) continue;
4221
4245
  activity = await signObject(activity, kp.privateKey, kp.multikey.id, {
4222
4246
  contextLoader,
4223
4247
  tracerProvider: this.tracerProvider
4224
4248
  });
4249
+ proofAdded = true;
4225
4250
  }
4251
+ if (proofAdded) activityJsonLd = void 0;
4226
4252
  }
4253
+ const opts = {
4254
+ context: this,
4255
+ orderingKey: options.orderingKey,
4256
+ collectionSync,
4257
+ immediate: options.immediate,
4258
+ activityJsonLd
4259
+ };
4227
4260
  const inboxes = extractInboxes({
4228
4261
  recipients: expandedRecipients,
4229
4262
  preferSharedInbox: options.preferSharedInbox,
@@ -4253,7 +4286,7 @@ var ContextImpl = class ContextImpl {
4253
4286
  actorIds: [...actorIds],
4254
4287
  sharedInbox
4255
4288
  }])),
4256
- activity: await activity.toJsonLd({
4289
+ activity: activityJsonLd != null ? detachSignature(activityJsonLd) : await activity.toJsonLd({
4257
4290
  format: "compact",
4258
4291
  contextLoader: this.contextLoader
4259
4292
  }),
@@ -4739,6 +4772,69 @@ var KvSpecDeterminer = class {
4739
4772
  function notFound(_request) {
4740
4773
  return new Response("Not Found", { status: 404 });
4741
4774
  }
4775
+ function hasTopLevelProof(value) {
4776
+ return typeof value === "object" && value != null && !Array.isArray(value) && globalThis.Object.prototype.hasOwnProperty.call(value, "proof");
4777
+ }
4778
+ function getContentFields(value) {
4779
+ if (typeof value !== "object" || value == null || Array.isArray(value)) return JSON.stringify(sortJsonKeys(value));
4780
+ const record = { ...value };
4781
+ delete record["@context"];
4782
+ delete record.proof;
4783
+ delete record.signature;
4784
+ return JSON.stringify(sortJsonKeys(record));
4785
+ }
4786
+ function getContextFields(value) {
4787
+ if (typeof value !== "object" || value == null || Array.isArray(value)) return JSON.stringify(sortJsonKeys(value));
4788
+ const context = value["@context"];
4789
+ return JSON.stringify(sortJsonKeys(context ?? null));
4790
+ }
4791
+ function getProofEntries(value) {
4792
+ if (typeof value !== "object" || value == null || Array.isArray(value)) return [];
4793
+ const proof = value.proof;
4794
+ if (proof == null) return [];
4795
+ return Array.isArray(proof) ? proof : [proof];
4796
+ }
4797
+ function normalizeProofEntry(proof) {
4798
+ const stripped = stripContexts(proof);
4799
+ if (typeof stripped !== "object" || stripped == null || Array.isArray(stripped)) return stripped;
4800
+ const record = { ...stripped };
4801
+ if (typeof record.created === "string") try {
4802
+ record.created = Temporal.Instant.from(record.created).toString();
4803
+ } catch {}
4804
+ return record;
4805
+ }
4806
+ function getProofEntryFields(proof) {
4807
+ return JSON.stringify(sortJsonKeys(normalizeProofEntry(proof)));
4808
+ }
4809
+ function getProofFields(value) {
4810
+ return JSON.stringify(getProofEntries(value).map(getProofEntryFields).sort());
4811
+ }
4812
+ async function getUncapturedProofs(activity, capturedJsonLd, contextLoader) {
4813
+ const capturedProofs = new Set(getProofEntries(capturedJsonLd).map(getProofEntryFields));
4814
+ const retainedProofs = [];
4815
+ for await (const proof of activity.getProofs({ contextLoader })) {
4816
+ const proofJsonLd = await proof.toJsonLd({
4817
+ format: "compact",
4818
+ contextLoader
4819
+ });
4820
+ if (!capturedProofs.has(getProofEntryFields(proofJsonLd))) retainedProofs.push(proof);
4821
+ }
4822
+ return retainedProofs;
4823
+ }
4824
+ function stripContexts(value) {
4825
+ if (Array.isArray(value)) return value.map(stripContexts);
4826
+ if (typeof value !== "object" || value == null) return value;
4827
+ const record = { ...value };
4828
+ delete record["@context"];
4829
+ delete record.signature;
4830
+ for (const key of globalThis.Object.keys(record)) record[key] = stripContexts(record[key]);
4831
+ return record;
4832
+ }
4833
+ function sortJsonKeys(value) {
4834
+ if (Array.isArray(value)) return value.map(sortJsonKeys);
4835
+ if (typeof value !== "object" || value == null) return value;
4836
+ return globalThis.Object.fromEntries(globalThis.Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, item]) => [key, sortJsonKeys(item)]));
4837
+ }
4742
4838
  function notAcceptable(_request) {
4743
4839
  return new Response("Not Acceptable", {
4744
4840
  status: 406,
@@ -2,24 +2,24 @@ import { Temporal } from "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { n as RouterError } from "./router-CrMLXoOr.mjs";
5
- import { n as version, t as name } from "./deno-CtG6TwTB.mjs";
6
- import { a as importJwk, o as validateCryptoKey, t as exportJwk } from "./key-QamJxksu.mjs";
7
- import { u as verifyRequest } from "./http-CUOxcDgn.mjs";
8
- import { n as getAuthenticatedDocumentLoader } from "./docloader-BxW_P_Rq.mjs";
5
+ import { n as version, t as name } from "./deno--OYa_lxj.mjs";
6
+ import { a as importJwk, o as validateCryptoKey, t as exportJwk } from "./key-D5e9c_yV.mjs";
7
+ import { u as verifyRequest } from "./http-C3d2DXwF.mjs";
8
+ import { n as getAuthenticatedDocumentLoader } from "./docloader-BXR2f83H.mjs";
9
9
  import { n as kvCache } from "./kv-cache-BrjuJue8.mjs";
10
- import { a as compactJsonLd, c as getNormalizationContextLoader, d as isInvalidUrlTypeError, f as signJsonLd, g as wrapContextLoaderForJsonLd, l as hasSignature, p as verifyCompactJsonLd, r as assertSafeJsonLd, s as detachSignature, t as InvalidContextReferenceError, u as isClearlyMalformedContextReference } from "./ld-B0eNy8WK.mjs";
11
- import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-lqq4KXYV.mjs";
12
- import { n as signObject, r as verifyObject } from "./proof-Bf8rYD7r.mjs";
10
+ import { a as compactJsonLd, c as getNormalizationContextLoader, d as isInvalidUrlTypeError, f as signJsonLd, g as wrapContextLoaderForJsonLd, l as hasSignature, p as verifyCompactJsonLd, r as assertSafeJsonLd, s as detachSignature, t as InvalidContextReferenceError, u as isClearlyMalformedContextReference } from "./ld-9qfz9rP_.mjs";
11
+ import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-R4ffU9Ht.mjs";
12
+ import { n as signObject, r as verifyObject } from "./proof-CiBs9Q4l.mjs";
13
13
  import { d as MAX_BODY_SIZE, f as readBoundedText, t as getNodeInfo, u as BodyTooLargeError } from "./client-DGNacEx_.mjs";
14
14
  import { t as nodeInfoToJson } from "./types-MNlFfxjv.mjs";
15
- import { n as actorsHaveSameOrigin, r as routeActivity } from "./inbox-DtThTl1D.mjs";
16
- import { t as FederationBuilderImpl } from "./builder-BBdPdHYx.mjs";
15
+ import { n as actorsHaveSameOrigin, r as routeActivity } from "./inbox-huUE3IP8.mjs";
16
+ import { t as FederationBuilderImpl } from "./builder-B5hI5Xsn.mjs";
17
17
  import { t as buildCollectionSynchronizationHeader } from "./collection-DI6YzYpd.mjs";
18
18
  import { t as KvKeyCache } from "./keycache-DBD6fzl1.mjs";
19
19
  import { t as acceptsJsonLd } from "./negotiation-TWEs6Cl8.mjs";
20
- import { t as hasMalformedKnownTemporalLiteral } from "./temporal-D6JQXNOJ.mjs";
20
+ import { t as hasMalformedKnownTemporalLiteral } from "./temporal-Dd-J410Z.mjs";
21
21
  import { t as createExponentialBackoffPolicy } from "./retry-Ddbq3AcK.mjs";
22
- import { i as sendActivity, n as extractInboxes, t as SendActivityError } from "./send-1tVKmxvT.mjs";
22
+ import { i as sendActivity, n as extractInboxes, t as SendActivityError } from "./send-DpvZgVSA.mjs";
23
23
  import { Activity, Collection, CollectionPage, CryptographicKey, Link, Multikey, Object as Object$1, OrderedCollection, OrderedCollectionPage, getTypeId, lookupObject, traverseCollection } from "@fedify/vocab";
24
24
  import { lookupWebFinger } from "@fedify/webfinger";
25
25
  import { SpanKind, SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
@@ -1665,7 +1665,8 @@ var FederationImpl = class extends FederationBuilderImpl {
1665
1665
  await this.sendActivity(keys, message.inboxes, activity, {
1666
1666
  collectionSync: message.collectionSync,
1667
1667
  orderingKey: message.orderingKey,
1668
- context
1668
+ context,
1669
+ activityJsonLd: hasTopLevelProof(message.activity) ? message.activity : void 0
1669
1670
  });
1670
1671
  }
1671
1672
  async #listenOutboxMessage(_, message, span) {
@@ -2072,7 +2073,7 @@ var FederationImpl = class extends FederationBuilderImpl {
2072
2073
  proofCreated = true;
2073
2074
  }
2074
2075
  }
2075
- let jsonLd = await activity.toJsonLd({
2076
+ let jsonLd = options.activityJsonLd != null ? detachSignature(options.activityJsonLd) : await activity.toJsonLd({
2076
2077
  format: "compact",
2077
2078
  contextLoader
2078
2079
  });
@@ -2789,7 +2790,17 @@ var ContextImpl = class ContextImpl {
2789
2790
  allowPrivateAddress: this.federation.allowPrivateAddress
2790
2791
  });
2791
2792
  }
2792
- sendActivity(sender, recipients, activity, options = {}) {
2793
+ async sendActivity(sender, recipients, activity, options = {}) {
2794
+ let activityJsonLd = void 0;
2795
+ let hasProof = activity.proofIds.length > 0;
2796
+ if (!hasProof) for await (const _ of activity.getProofs({ contextLoader: this.contextLoader })) {
2797
+ hasProof = true;
2798
+ break;
2799
+ }
2800
+ if (hasProof) {
2801
+ activityJsonLd = await activity.toJsonLd({ contextLoader: this.contextLoader });
2802
+ if (!hasTopLevelProof(activityJsonLd)) activityJsonLd = void 0;
2803
+ }
2793
2804
  return this.tracerProvider.getTracer(name, version).startActiveSpan(this.federation.outboxQueue == null || options.immediate ? "activitypub.outbox" : "activitypub.fanout", {
2794
2805
  kind: this.federation.outboxQueue == null || options.immediate ? SpanKind.CLIENT : SpanKind.PRODUCER,
2795
2806
  attributes: {
@@ -2802,7 +2813,7 @@ var ContextImpl = class ContextImpl {
2802
2813
  }, async (span) => {
2803
2814
  try {
2804
2815
  if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
2805
- await this.sendActivityInternal(sender, recipients, activity, options, span);
2816
+ await this.sendActivityInternal(sender, recipients, activity, options, span, activityJsonLd);
2806
2817
  } catch (e) {
2807
2818
  span.setStatus({
2808
2819
  code: SpanStatusCode.ERROR,
@@ -2814,7 +2825,7 @@ var ContextImpl = class ContextImpl {
2814
2825
  }
2815
2826
  });
2816
2827
  }
2817
- async sendActivityInternal(sender, recipients, activity, options, span) {
2828
+ async sendActivityInternal(sender, recipients, activity, options, span, activityJsonLd) {
2818
2829
  const logger = getLogger([
2819
2830
  "fedify",
2820
2831
  "federation",
@@ -2861,14 +2872,23 @@ var ContextImpl = class ContextImpl {
2861
2872
  if (!(error instanceof RouterError)) throw error;
2862
2873
  }
2863
2874
  } else expandedRecipients = [recipients];
2864
- const opts = {
2865
- context: this,
2866
- orderingKey: options.orderingKey,
2867
- collectionSync,
2868
- immediate: options.immediate
2869
- };
2870
2875
  span.setAttribute("activitypub.inboxes", expandedRecipients.length);
2871
2876
  for (const activityTransformer of this.federation.activityTransformers) activity = activityTransformer(activity, this);
2877
+ let contextMatches = false;
2878
+ if (activityJsonLd != null) {
2879
+ const capturedJsonLd = activityJsonLd;
2880
+ const transformedJsonLd = await activity.toJsonLd({
2881
+ format: "compact",
2882
+ contextLoader: this.contextLoader
2883
+ });
2884
+ const contentChanged = getContentFields(transformedJsonLd) !== getContentFields(capturedJsonLd);
2885
+ contextMatches = getContextFields(transformedJsonLd) === getContextFields(capturedJsonLd);
2886
+ const proofsChanged = getProofFields(transformedJsonLd) !== getProofFields(capturedJsonLd);
2887
+ if (contentChanged || !contextMatches && proofsChanged) {
2888
+ activity = activity.clone({ proofs: await getUncapturedProofs(activity, capturedJsonLd, this.contextLoader) });
2889
+ activityJsonLd = void 0;
2890
+ } else if (proofsChanged) activityJsonLd = void 0;
2891
+ }
2872
2892
  span?.setAttribute("activitypub.activity.id", activity?.id?.href ?? "");
2873
2893
  if (activity.actorId == null) {
2874
2894
  logger.error("Activity {activityId} to send does not have an actor.", {
@@ -2879,14 +2899,27 @@ var ContextImpl = class ContextImpl {
2879
2899
  }
2880
2900
  if (actorKeyPairs != null) {
2881
2901
  const contextLoader = this.contextLoader;
2902
+ if (actorKeyPairs.some((kp) => kp.privateKey.algorithm.name === "Ed25519" && kp.multikey.id != null)) {
2903
+ if (activityJsonLd != null && !contextMatches) activity = activity.clone({ proofs: [] });
2904
+ }
2905
+ let proofAdded = false;
2882
2906
  for (const kp of actorKeyPairs) {
2883
2907
  if (kp.privateKey.algorithm.name !== "Ed25519" || kp.multikey.id == null) continue;
2884
2908
  activity = await signObject(activity, kp.privateKey, kp.multikey.id, {
2885
2909
  contextLoader,
2886
2910
  tracerProvider: this.tracerProvider
2887
2911
  });
2912
+ proofAdded = true;
2888
2913
  }
2914
+ if (proofAdded) activityJsonLd = void 0;
2889
2915
  }
2916
+ const opts = {
2917
+ context: this,
2918
+ orderingKey: options.orderingKey,
2919
+ collectionSync,
2920
+ immediate: options.immediate,
2921
+ activityJsonLd
2922
+ };
2890
2923
  const inboxes = extractInboxes({
2891
2924
  recipients: expandedRecipients,
2892
2925
  preferSharedInbox: options.preferSharedInbox,
@@ -2916,7 +2949,7 @@ var ContextImpl = class ContextImpl {
2916
2949
  actorIds: [...actorIds],
2917
2950
  sharedInbox
2918
2951
  }])),
2919
- activity: await activity.toJsonLd({
2952
+ activity: activityJsonLd != null ? detachSignature(activityJsonLd) : await activity.toJsonLd({
2920
2953
  format: "compact",
2921
2954
  contextLoader: this.contextLoader
2922
2955
  }),
@@ -3402,6 +3435,69 @@ var KvSpecDeterminer = class {
3402
3435
  function notFound(_request) {
3403
3436
  return new Response("Not Found", { status: 404 });
3404
3437
  }
3438
+ function hasTopLevelProof(value) {
3439
+ return typeof value === "object" && value != null && !Array.isArray(value) && globalThis.Object.prototype.hasOwnProperty.call(value, "proof");
3440
+ }
3441
+ function getContentFields(value) {
3442
+ if (typeof value !== "object" || value == null || Array.isArray(value)) return JSON.stringify(sortJsonKeys(value));
3443
+ const record = { ...value };
3444
+ delete record["@context"];
3445
+ delete record.proof;
3446
+ delete record.signature;
3447
+ return JSON.stringify(sortJsonKeys(record));
3448
+ }
3449
+ function getContextFields(value) {
3450
+ if (typeof value !== "object" || value == null || Array.isArray(value)) return JSON.stringify(sortJsonKeys(value));
3451
+ const context = value["@context"];
3452
+ return JSON.stringify(sortJsonKeys(context ?? null));
3453
+ }
3454
+ function getProofEntries(value) {
3455
+ if (typeof value !== "object" || value == null || Array.isArray(value)) return [];
3456
+ const proof = value.proof;
3457
+ if (proof == null) return [];
3458
+ return Array.isArray(proof) ? proof : [proof];
3459
+ }
3460
+ function normalizeProofEntry(proof) {
3461
+ const stripped = stripContexts(proof);
3462
+ if (typeof stripped !== "object" || stripped == null || Array.isArray(stripped)) return stripped;
3463
+ const record = { ...stripped };
3464
+ if (typeof record.created === "string") try {
3465
+ record.created = Temporal.Instant.from(record.created).toString();
3466
+ } catch {}
3467
+ return record;
3468
+ }
3469
+ function getProofEntryFields(proof) {
3470
+ return JSON.stringify(sortJsonKeys(normalizeProofEntry(proof)));
3471
+ }
3472
+ function getProofFields(value) {
3473
+ return JSON.stringify(getProofEntries(value).map(getProofEntryFields).sort());
3474
+ }
3475
+ async function getUncapturedProofs(activity, capturedJsonLd, contextLoader) {
3476
+ const capturedProofs = new Set(getProofEntries(capturedJsonLd).map(getProofEntryFields));
3477
+ const retainedProofs = [];
3478
+ for await (const proof of activity.getProofs({ contextLoader })) {
3479
+ const proofJsonLd = await proof.toJsonLd({
3480
+ format: "compact",
3481
+ contextLoader
3482
+ });
3483
+ if (!capturedProofs.has(getProofEntryFields(proofJsonLd))) retainedProofs.push(proof);
3484
+ }
3485
+ return retainedProofs;
3486
+ }
3487
+ function stripContexts(value) {
3488
+ if (Array.isArray(value)) return value.map(stripContexts);
3489
+ if (typeof value !== "object" || value == null) return value;
3490
+ const record = { ...value };
3491
+ delete record["@context"];
3492
+ delete record.signature;
3493
+ for (const key of globalThis.Object.keys(record)) record[key] = stripContexts(record[key]);
3494
+ return record;
3495
+ }
3496
+ function sortJsonKeys(value) {
3497
+ if (Array.isArray(value)) return value.map(sortJsonKeys);
3498
+ if (typeof value !== "object" || value == null) return value;
3499
+ return globalThis.Object.fromEntries(globalThis.Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, item]) => [key, sortJsonKeys(item)]));
3500
+ }
3405
3501
  function notAcceptable(_request) {
3406
3502
  return new Response("Not Acceptable", {
3407
3503
  status: 406,
@@ -1,5 +1,5 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as FederationImpl } from "./middleware-II_yrP7N.mjs";
4
+ import { n as FederationImpl } from "./middleware-BhYpLGA_.mjs";
5
5
  export { FederationImpl };