@fedify/fedify 2.4.0-dev.1988 → 2.4.0-dev.1995
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.
- package/dist/{builder-Cts78G9N.mjs → builder-D0DxgOV5.mjs} +2 -2
- package/dist/compat/transformers.test.mjs +1 -1
- package/dist/compound-proof-CJnWc6c-.mjs +495 -0
- package/dist/{deno-BgMEefEr.mjs → deno-CABhzCb_.mjs} +1 -1
- package/dist/{docloader-DEYs99FZ.mjs → docloader-BbGSL0Xq.mjs} +2 -2
- package/dist/federation/builder.test.mjs +1 -1
- package/dist/federation/handler-compound-proof.test.d.mts +2 -0
- package/dist/federation/handler-compound-proof.test.mjs +152 -0
- package/dist/federation/handler.test.mjs +5 -5
- package/dist/federation/idempotency.test.mjs +2 -2
- package/dist/federation/metrics.test.mjs +1 -1
- package/dist/federation/middleware.test.mjs +7 -7
- package/dist/federation/mod.cjs +1 -1
- package/dist/federation/mod.js +1 -1
- package/dist/federation/send.test.mjs +3 -3
- package/dist/federation/tasks/enqueue.test.mjs +1 -1
- package/dist/federation/tasks/tasks.test.mjs +2 -2
- package/dist/federation/temporal.test.mjs +1 -1
- package/dist/federation/webfinger.test.mjs +1 -1
- package/dist/{federation-DzPUG_AP.cjs → federation-DCEHQk4G.cjs} +542 -43
- package/dist/{http-CgwqtxAy.js → http-9WyuldYh.js} +2 -2
- package/dist/{http-Z3anvCd-.cjs → http-CRFns-UE.cjs} +2 -2
- package/dist/{http-CtidZS_t.mjs → http-D9urSO2D.mjs} +3 -3
- package/dist/{key-Cwbg5ggV.mjs → key-Ccs8cnd7.mjs} +2 -2
- package/dist/{kv-cache-BM2-w-Wq.cjs → kv-cache-DXT9vmIg.cjs} +1 -1
- package/dist/{kv-cache-CykuL0ya.js → kv-cache-DtmXhPXG.js} +1 -1
- package/dist/{kv-cache-DHiR8yTf.mjs → kv-cache-lKyFn_Dn.mjs} +1 -1
- package/dist/{ld-CzhdWyTt.mjs → ld-qup__MAW.mjs} +3 -3
- package/dist/map-local-context-conflict-DOYEEJ3u.mjs +204 -0
- package/dist/map-local-create-note-B0SeXJms.mjs +214 -0
- package/dist/{metrics-CrqsHP0l.mjs → metrics-CISXCvB1.mjs} +1 -1
- package/dist/{middleware-DCpiAsZi.mjs → middleware-CzvJvPHK.mjs} +52 -20
- package/dist/{middleware-CZpYxSKD.mjs → middleware-DkrU2WhY.mjs} +1 -1
- package/dist/{middleware-CeCj5caM.js → middleware-DsfWajaD.js} +511 -12
- package/dist/mod.cjs +19 -19
- package/dist/mod.js +4 -4
- package/dist/nodeinfo/handler.test.mjs +1 -1
- package/dist/{proof-CUghqhAf.js → owner-BIPEZGiU.js} +1239 -1139
- package/dist/{owner-BqVXJLW8.mjs → owner-CRoq8Spb.mjs} +2 -2
- package/dist/{proof-DHu5zI0Z.cjs → owner-CVA7L9MA.cjs} +1252 -1140
- package/dist/{proof-DL8Iid3a.mjs → proof-BDxNVZ3E.mjs} +176 -76
- package/dist/{send-6jS4xFsX.mjs → send-BzM5GLyn.mjs} +3 -3
- package/dist/sig/compound-proof-discovery.test.d.mts +2 -0
- package/dist/sig/compound-proof-discovery.test.mjs +331 -0
- package/dist/sig/compound-proof-verification.test.d.mts +2 -0
- package/dist/sig/compound-proof-verification.test.mjs +465 -0
- package/dist/sig/compound-proof.test.d.mts +2 -0
- package/dist/sig/compound-proof.test.mjs +324 -0
- package/dist/sig/http.test.mjs +2 -2
- package/dist/sig/key.test.mjs +1 -1
- package/dist/sig/ld.test.mjs +2 -2
- package/dist/sig/mod.cjs +17 -17
- package/dist/sig/mod.js +2 -2
- package/dist/sig/owner.test.mjs +1 -1
- package/dist/sig/proof.test.mjs +1 -1
- package/dist/{temporal-DcyzeytP.mjs → temporal-Dv6Qv-8k.mjs} +1 -1
- package/dist/testing/kv.test.mjs +1 -1
- package/dist/utils/docloader.test.mjs +2 -2
- package/dist/utils/kv-cache.test.mjs +1 -1
- package/dist/utils/mod.cjs +1 -1
- package/dist/utils/mod.js +1 -1
- package/package.json +6 -6
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { Temporal } from "temporal-polyfill";
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import { n as version, t as name } from "./deno-
|
|
5
|
-
import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-
|
|
6
|
-
import { r as fetchKey, s as validateCryptoKey } from "./key-
|
|
4
|
+
import { n as version, t as name } from "./deno-CABhzCb_.mjs";
|
|
5
|
+
import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-CISXCvB1.mjs";
|
|
6
|
+
import { r as fetchKey, s as validateCryptoKey } from "./key-Ccs8cnd7.mjs";
|
|
7
7
|
import { n as preloadedOnlyDocumentLoader } from "./public-audience-Cc2YJ3ip.mjs";
|
|
8
8
|
import { r as normalizeOutgoingActivityJsonLd } from "./outgoing-jsonld-B5Ne7JoL.mjs";
|
|
9
|
-
import { c as getNormalizationContextLoader } from "./ld-
|
|
9
|
+
import { c as getNormalizationContextLoader } from "./ld-qup__MAW.mjs";
|
|
10
10
|
import { getLogger } from "@logtape/logtape";
|
|
11
11
|
import { Activity, DataIntegrityProof, Multikey, getTypeId } from "@fedify/vocab";
|
|
12
12
|
import { SpanStatusCode, trace } from "@opentelemetry/api";
|
|
@@ -164,6 +164,30 @@ async function signObject(object, privateKey, keyId, options = {}) {
|
|
|
164
164
|
async function verifyProof(jsonLd, proof, options = {}) {
|
|
165
165
|
return await verifyProofWithMessageDigestCache(jsonLd, proof, options);
|
|
166
166
|
}
|
|
167
|
+
/**
|
|
168
|
+
* Verifies the one direct literal proof on a map-local secured document.
|
|
169
|
+
*
|
|
170
|
+
* Unlike {@link verifyProof}, this removes only the literal `proof` member
|
|
171
|
+
* from the message digest. JSON-LD aliases remain part of the signed input,
|
|
172
|
+
* and the document's received `@context` is not replaced by the proof
|
|
173
|
+
* configuration context.
|
|
174
|
+
*
|
|
175
|
+
* @internal
|
|
176
|
+
*/
|
|
177
|
+
async function verifyMapLocalProof(jsonLd, options = {}) {
|
|
178
|
+
if (!isJsonLdNode(jsonLd) || !globalThis.Object.hasOwn(jsonLd, "proof") || !isJsonLdNode(jsonLd.proof)) return null;
|
|
179
|
+
const proofContextLoader = getNormalizationContextLoader(preloadedOnlyDocumentLoader);
|
|
180
|
+
try {
|
|
181
|
+
const [candidate] = await parseRawProofCandidates(jsonLd, [jsonLd.proof], options, proofContextLoader);
|
|
182
|
+
if (candidate.proof == null) return null;
|
|
183
|
+
return await verifyProofWithMessageDigestCache(jsonLd, candidate.proof, options, {
|
|
184
|
+
proofContextLoader,
|
|
185
|
+
proofPropertyMode: "literal"
|
|
186
|
+
}, candidate);
|
|
187
|
+
} catch {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
167
191
|
async function verifyProofWithMessageDigestCache(jsonLd, proof, options, messageDigestCache = {}, rawProofCandidate, keyIdBoundByCaller = false) {
|
|
168
192
|
return await (options.tracerProvider ?? trace.getTracerProvider()).getTracer(name, version).startActiveSpan("object_integrity_proofs.verify", async (span) => {
|
|
169
193
|
const start = performance.now();
|
|
@@ -234,10 +258,11 @@ async function getJsonLdPropertyNames(jsonLd, propertyIri, defaults, documentLoa
|
|
|
234
258
|
async function getProofPropertyNames(jsonLd, documentLoader = preloadedOnlyDocumentLoader) {
|
|
235
259
|
return await getJsonLdPropertyNames(jsonLd, SECURITY_PROOF, ["proof", SECURITY_PROOF], documentLoader) ?? /* @__PURE__ */ new Set(["proof", SECURITY_PROOF]);
|
|
236
260
|
}
|
|
237
|
-
async function createProofMessageDigests(jsonLd, proofContextLoader, context) {
|
|
261
|
+
async function createProofMessageDigests(jsonLd, proofContextLoader, context, proofPropertyMode = "jsonLd") {
|
|
238
262
|
const msg = { ...jsonLd };
|
|
239
|
-
|
|
240
|
-
|
|
263
|
+
if (proofPropertyMode === "literal") delete msg.proof;
|
|
264
|
+
else for (const property of await getProofPropertyNames(msg, proofContextLoader)) delete msg[property];
|
|
265
|
+
if (proofPropertyMode === "jsonLd" && context != null) msg["@context"] = structuredClone(context);
|
|
241
266
|
const encoder = new TextEncoder();
|
|
242
267
|
const digest = async (value) => {
|
|
243
268
|
const bytes = encoder.encode(serialize(value));
|
|
@@ -554,13 +579,15 @@ async function verifyProofInternal(jsonLd, proof, options, messageDigestCache, r
|
|
|
554
579
|
const messageDigestValues = messageDigestCache.values ??= /* @__PURE__ */ new Map();
|
|
555
580
|
let messageDigestsPromise = messageDigestValues.get(messageDigestKey);
|
|
556
581
|
if (messageDigestsPromise == null) {
|
|
557
|
-
messageDigestsPromise = createProofMessageDigests(jsonLd, messageDigestCache.proofContextLoader, proofConfiguration.context);
|
|
582
|
+
messageDigestsPromise = createProofMessageDigests(jsonLd, messageDigestCache.proofContextLoader, proofConfiguration.context, messageDigestCache.proofPropertyMode);
|
|
558
583
|
messageDigestValues.set(messageDigestKey, messageDigestsPromise);
|
|
559
584
|
}
|
|
560
585
|
const messageDigests = await messageDigestsPromise;
|
|
561
586
|
if (await verifyCandidate(messageDigests.onWire)) return publicKey;
|
|
562
|
-
|
|
563
|
-
|
|
587
|
+
if (messageDigestCache.proofPropertyMode !== "literal") {
|
|
588
|
+
const normalizedDigest = await messageDigests.normalized();
|
|
589
|
+
if (normalizedDigest != null && await verifyCandidate(normalizedDigest)) return publicKey;
|
|
590
|
+
}
|
|
564
591
|
if (fetchedKey.cached) {
|
|
565
592
|
logger.debug("Failed to verify the proof with the cached key {keyId}; retrying with the freshly fetched key...", {
|
|
566
593
|
keyId: proof.verificationMethodId.href,
|
|
@@ -652,59 +679,55 @@ async function expandPortableObjectRoot(jsonLd, contextLoader) {
|
|
|
652
679
|
}
|
|
653
680
|
};
|
|
654
681
|
}
|
|
655
|
-
|
|
656
|
-
* Verifies the FEP-ef61 Object Integrity Proof policy for a portable object.
|
|
657
|
-
*
|
|
658
|
-
* This applies the FEP-2277 core-type classification to the top-level JSON-LD
|
|
659
|
-
* node. Portable actors, activities, and objects require proofs. A portable
|
|
660
|
-
* collection without a proof is reported separately so a caller can apply a
|
|
661
|
-
* gateway trust policy. Embedded portable objects are not traversed.
|
|
662
|
-
*
|
|
663
|
-
* Every proof must use a DID URL whose DID matches the portable object's
|
|
664
|
-
* authority, and every proof must pass {@link verifyProof}.
|
|
665
|
-
*
|
|
666
|
-
* @param jsonLd The JSON-LD document to verify.
|
|
667
|
-
* @param options Additional options. See also
|
|
668
|
-
* {@link VerifyPortableObjectProofOptions}.
|
|
669
|
-
* @returns The detailed portable proof-policy result.
|
|
670
|
-
* @throws {TypeError} If the input is not a single JSON-LD object or has a
|
|
671
|
-
* malformed portable ID.
|
|
672
|
-
* @since 2.4.0
|
|
673
|
-
*/
|
|
674
|
-
async function verifyPortableObjectProof(jsonLd, options = {}) {
|
|
682
|
+
async function preparePortableObjectProof(jsonLd, options) {
|
|
675
683
|
if (isJsonLdNode(jsonLd) && typeof jsonLd["@id"] === "string" && !PORTABLE_OBJECT_ID_PATTERN.test(jsonLd["@id"])) return {
|
|
676
|
-
|
|
677
|
-
|
|
684
|
+
prepared: false,
|
|
685
|
+
result: {
|
|
686
|
+
verified: false,
|
|
687
|
+
reason: { type: "notPortableObject" }
|
|
688
|
+
}
|
|
678
689
|
};
|
|
679
690
|
const { root, proofContextLoader } = await expandPortableObjectRoot(jsonLd, options.contextLoader);
|
|
680
691
|
const id = root["@id"];
|
|
681
692
|
if (typeof id !== "string" || !PORTABLE_OBJECT_ID_PATTERN.test(id)) return {
|
|
682
|
-
|
|
683
|
-
|
|
693
|
+
prepared: false,
|
|
694
|
+
result: {
|
|
695
|
+
verified: false,
|
|
696
|
+
reason: { type: "notPortableObject" }
|
|
697
|
+
}
|
|
684
698
|
};
|
|
685
699
|
const objectId = parseIri(id);
|
|
686
700
|
getFe34Origin(objectId);
|
|
687
701
|
const objectType = classifyFep2277CoreType(root);
|
|
688
702
|
if (objectType === "verificationMethod" || objectType === "publicKey" || objectType === "link") return {
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
703
|
+
prepared: false,
|
|
704
|
+
result: {
|
|
705
|
+
verified: false,
|
|
706
|
+
reason: {
|
|
707
|
+
type: "unsupportedObjectType",
|
|
708
|
+
objectType
|
|
709
|
+
}
|
|
693
710
|
}
|
|
694
711
|
};
|
|
695
712
|
const proofValues = root[SECURITY_PROOF];
|
|
696
|
-
if (proofValues == null || Array.isArray(proofValues) && proofValues.length < 1) return
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
713
|
+
if (proofValues == null || Array.isArray(proofValues) && proofValues.length < 1) return {
|
|
714
|
+
prepared: false,
|
|
715
|
+
result: objectType === "collection" ? {
|
|
716
|
+
verified: false,
|
|
717
|
+
reason: { type: "unsecuredCollection" }
|
|
718
|
+
} : {
|
|
719
|
+
verified: false,
|
|
720
|
+
reason: { type: "missingProof" }
|
|
721
|
+
}
|
|
702
722
|
};
|
|
703
723
|
if (!Array.isArray(proofValues)) return {
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
724
|
+
prepared: false,
|
|
725
|
+
result: {
|
|
726
|
+
verified: false,
|
|
727
|
+
reason: {
|
|
728
|
+
type: "invalidProof",
|
|
729
|
+
proofIndex: 0
|
|
730
|
+
}
|
|
708
731
|
}
|
|
709
732
|
};
|
|
710
733
|
const rawProofValues = isJsonLdNode(jsonLd) ? await getRawProofValues(jsonLd, proofContextLoader) : [];
|
|
@@ -712,10 +735,13 @@ async function verifyPortableObjectProof(jsonLd, options = {}) {
|
|
|
712
735
|
for (let proofIndex = 0; proofIndex < proofValues.length; proofIndex++) {
|
|
713
736
|
const proofValue = proofValues[proofIndex];
|
|
714
737
|
if (!hasValidPortableProofShape(proofValue)) return {
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
738
|
+
prepared: false,
|
|
739
|
+
result: {
|
|
740
|
+
verified: false,
|
|
741
|
+
reason: {
|
|
742
|
+
type: "invalidProof",
|
|
743
|
+
proofIndex
|
|
744
|
+
}
|
|
719
745
|
}
|
|
720
746
|
};
|
|
721
747
|
let proof;
|
|
@@ -723,10 +749,13 @@ async function verifyPortableObjectProof(jsonLd, options = {}) {
|
|
|
723
749
|
proof = await DataIntegrityProof.fromJsonLd(proofValue, options);
|
|
724
750
|
} catch {
|
|
725
751
|
return {
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
752
|
+
prepared: false,
|
|
753
|
+
result: {
|
|
754
|
+
verified: false,
|
|
755
|
+
reason: {
|
|
756
|
+
type: "invalidProof",
|
|
757
|
+
proofIndex
|
|
758
|
+
}
|
|
730
759
|
}
|
|
731
760
|
};
|
|
732
761
|
}
|
|
@@ -735,18 +764,24 @@ async function verifyPortableObjectProof(jsonLd, options = {}) {
|
|
|
735
764
|
for (let proofIndex = 0; proofIndex < proofs.length; proofIndex++) {
|
|
736
765
|
const verificationMethod = proofs[proofIndex].verificationMethodId;
|
|
737
766
|
if (verificationMethod == null) return {
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
767
|
+
prepared: false,
|
|
768
|
+
result: {
|
|
769
|
+
verified: false,
|
|
770
|
+
reason: {
|
|
771
|
+
type: "invalidProof",
|
|
772
|
+
proofIndex
|
|
773
|
+
}
|
|
742
774
|
}
|
|
743
775
|
};
|
|
744
776
|
if (verificationMethod.protocol !== "did:") return {
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
777
|
+
prepared: false,
|
|
778
|
+
result: {
|
|
779
|
+
verified: false,
|
|
780
|
+
reason: {
|
|
781
|
+
type: "unsupportedVerificationMethod",
|
|
782
|
+
proofIndex,
|
|
783
|
+
verificationMethod
|
|
784
|
+
}
|
|
750
785
|
}
|
|
751
786
|
};
|
|
752
787
|
try {
|
|
@@ -754,24 +789,89 @@ async function verifyPortableObjectProof(jsonLd, options = {}) {
|
|
|
754
789
|
} catch (error) {
|
|
755
790
|
if (!(error instanceof TypeError)) throw error;
|
|
756
791
|
return {
|
|
792
|
+
prepared: false,
|
|
793
|
+
result: {
|
|
794
|
+
verified: false,
|
|
795
|
+
reason: {
|
|
796
|
+
type: "unsupportedVerificationMethod",
|
|
797
|
+
proofIndex,
|
|
798
|
+
verificationMethod
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
if (!haveSameFe34Origin(objectId, verificationMethod)) return {
|
|
804
|
+
prepared: false,
|
|
805
|
+
result: {
|
|
757
806
|
verified: false,
|
|
758
807
|
reason: {
|
|
759
|
-
type: "
|
|
808
|
+
type: "verificationMethodMismatch",
|
|
760
809
|
proofIndex,
|
|
810
|
+
objectId,
|
|
761
811
|
verificationMethod
|
|
762
812
|
}
|
|
763
|
-
};
|
|
764
|
-
}
|
|
765
|
-
if (!haveSameFe34Origin(objectId, verificationMethod)) return {
|
|
766
|
-
verified: false,
|
|
767
|
-
reason: {
|
|
768
|
-
type: "verificationMethodMismatch",
|
|
769
|
-
proofIndex,
|
|
770
|
-
objectId,
|
|
771
|
-
verificationMethod
|
|
772
813
|
}
|
|
773
814
|
};
|
|
774
815
|
}
|
|
816
|
+
return {
|
|
817
|
+
prepared: true,
|
|
818
|
+
objectId,
|
|
819
|
+
proofs,
|
|
820
|
+
rawProofValues,
|
|
821
|
+
proofContextLoader
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
/**
|
|
825
|
+
* Applies portable-object proof policy to one map-local cryptographic result.
|
|
826
|
+
*
|
|
827
|
+
* @internal
|
|
828
|
+
*/
|
|
829
|
+
async function verifyPortableObjectProofPolicy(jsonLd, key, options = {}) {
|
|
830
|
+
const policyOptions = {
|
|
831
|
+
...options,
|
|
832
|
+
contextLoader: preloadedOnlyDocumentLoader
|
|
833
|
+
};
|
|
834
|
+
const prepared = await preparePortableObjectProof(jsonLd, policyOptions);
|
|
835
|
+
if (!prepared.prepared) return prepared.result;
|
|
836
|
+
const policyProof = prepared.proofs[0];
|
|
837
|
+
const literalProof = isJsonLdNode(jsonLd) && isJsonLdNode(jsonLd.proof) ? (await parseRawProofCandidates(jsonLd, [jsonLd.proof], policyOptions, prepared.proofContextLoader))[0]?.proof : null;
|
|
838
|
+
const verificationMethod = policyProof?.verificationMethodId;
|
|
839
|
+
if (prepared.proofs.length !== 1 || key == null || policyProof == null || literalProof == null || !sameProof(policyProof, literalProof) || verificationMethod == null || key.id?.href !== verificationMethod.href) return {
|
|
840
|
+
verified: false,
|
|
841
|
+
reason: {
|
|
842
|
+
type: "invalidProof",
|
|
843
|
+
proofIndex: prepared.proofs.length > 1 ? 1 : 0
|
|
844
|
+
}
|
|
845
|
+
};
|
|
846
|
+
return {
|
|
847
|
+
verified: true,
|
|
848
|
+
keys: [key],
|
|
849
|
+
objectId: formatIri(prepared.objectId)
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
/**
|
|
853
|
+
* Verifies the FEP-ef61 Object Integrity Proof policy for a portable object.
|
|
854
|
+
*
|
|
855
|
+
* This applies the FEP-2277 core-type classification to the top-level JSON-LD
|
|
856
|
+
* node. Portable actors, activities, and objects require proofs. A portable
|
|
857
|
+
* collection without a proof is reported separately so a caller can apply a
|
|
858
|
+
* gateway trust policy. Embedded portable objects are not traversed.
|
|
859
|
+
*
|
|
860
|
+
* Every proof must use a DID URL whose DID matches the portable object's
|
|
861
|
+
* authority, and every proof must pass {@link verifyProof}.
|
|
862
|
+
*
|
|
863
|
+
* @param jsonLd The JSON-LD document to verify.
|
|
864
|
+
* @param options Additional options. See also
|
|
865
|
+
* {@link VerifyPortableObjectProofOptions}.
|
|
866
|
+
* @returns The detailed portable proof-policy result.
|
|
867
|
+
* @throws {TypeError} If the input is not a single JSON-LD object or has a
|
|
868
|
+
* malformed portable ID.
|
|
869
|
+
* @since 2.4.0
|
|
870
|
+
*/
|
|
871
|
+
async function verifyPortableObjectProof(jsonLd, options = {}) {
|
|
872
|
+
const prepared = await preparePortableObjectProof(jsonLd, options);
|
|
873
|
+
if (!prepared.prepared) return prepared.result;
|
|
874
|
+
const { proofs, rawProofValues, proofContextLoader } = prepared;
|
|
775
875
|
const keys = [];
|
|
776
876
|
const rawProofCandidatePool = {
|
|
777
877
|
candidates: await parseRawProofCandidates(jsonLd, rawProofValues, options, proofContextLoader),
|
|
@@ -890,4 +990,4 @@ function hasCryptographicOrigin(iri) {
|
|
|
890
990
|
return /^did:/i.test(iri) || /^ap(?:\+ef61)?:\/\//i.test(iri);
|
|
891
991
|
}
|
|
892
992
|
//#endregion
|
|
893
|
-
export {
|
|
993
|
+
export { verifyObject as a, verifyProof as c, verifyMapLocalProof as i, hasProofLike as n, verifyPortableObjectProof as o, signObject as r, verifyPortableObjectProofPolicy as s, createProof as t };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import "temporal-polyfill";
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import { n as version, t as name } from "./deno-
|
|
5
|
-
import { n as getDurationMs, r as getFederationMetrics } from "./metrics-
|
|
6
|
-
import { n as doubleKnock } from "./http-
|
|
4
|
+
import { n as version, t as name } from "./deno-CABhzCb_.mjs";
|
|
5
|
+
import { n as getDurationMs, r as getFederationMetrics } from "./metrics-CISXCvB1.mjs";
|
|
6
|
+
import { n as doubleKnock } from "./http-D9urSO2D.mjs";
|
|
7
7
|
import { getLogger } from "@logtape/logtape";
|
|
8
8
|
import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
|
|
9
9
|
import { FetchError, UrlError, validatePublicUrl } from "@fedify/vocab-runtime";
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import "temporal-polyfill";
|
|
2
|
+
import "urlpattern-polyfill";
|
|
3
|
+
globalThis.addEventListener = () => {};
|
|
4
|
+
import { t as assertEquals } from "../assert_equals-CBlRfA-_.mjs";
|
|
5
|
+
import "../std__assert-CxFT0MNt.mjs";
|
|
6
|
+
import { n as inspectCompoundPortableObjectApplicability, t as discoverCompoundProofDocuments } from "../compound-proof-CJnWc6c-.mjs";
|
|
7
|
+
import { t as map_local_create_note_default } from "../map-local-create-note-B0SeXJms.mjs";
|
|
8
|
+
import { test } from "@fedify/fixture";
|
|
9
|
+
//#region src/sig/compound-proof-discovery.test.ts
|
|
10
|
+
const limits = {
|
|
11
|
+
maxDepth: 16,
|
|
12
|
+
maxMaps: 32,
|
|
13
|
+
maxProofs: 8,
|
|
14
|
+
maxBytes: 16384
|
|
15
|
+
};
|
|
16
|
+
test("portable applicability is bounded and ignores metadata", () => {
|
|
17
|
+
assertEquals(inspectCompoundPortableObjectApplicability({
|
|
18
|
+
"@context": { id: "ap://did:key:context/object" },
|
|
19
|
+
proof: { id: "ap://did:key:proof/object" },
|
|
20
|
+
object: { id: "https://example.com/objects/1" }
|
|
21
|
+
}, limits), "absent");
|
|
22
|
+
assertEquals(inspectCompoundPortableObjectApplicability({ object: [{ "@id": "AP+EF61://did:key:child/objects/1" }] }, limits), "present");
|
|
23
|
+
assertEquals(inspectCompoundPortableObjectApplicability({ values: new Array(2e4).fill(0) }, {
|
|
24
|
+
...limits,
|
|
25
|
+
maxBytes: 1e3
|
|
26
|
+
}), "indeterminate");
|
|
27
|
+
});
|
|
28
|
+
test("discoverCompoundProofDocuments() returns immutable deepest-first candidates", () => {
|
|
29
|
+
const input = {
|
|
30
|
+
id: "ap://example.test/activities/1",
|
|
31
|
+
object: {
|
|
32
|
+
id: "ap://example.test/objects/1",
|
|
33
|
+
content: "one",
|
|
34
|
+
proof: {
|
|
35
|
+
type: "DataIntegrityProof",
|
|
36
|
+
proofValue: "zInner"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
items: [{
|
|
40
|
+
id: "ap://example.test/objects/2",
|
|
41
|
+
content: "two",
|
|
42
|
+
proof: {
|
|
43
|
+
type: "DataIntegrityProof",
|
|
44
|
+
proofValue: "zArray"
|
|
45
|
+
}
|
|
46
|
+
}],
|
|
47
|
+
"a/b~c": {
|
|
48
|
+
"@id": "ap://example.test/objects/3",
|
|
49
|
+
proof: {
|
|
50
|
+
type: "DataIntegrityProof",
|
|
51
|
+
proofValue: "zEscaped"
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
proof: {
|
|
55
|
+
type: "DataIntegrityProof",
|
|
56
|
+
proofValue: "zOuter",
|
|
57
|
+
proof: {
|
|
58
|
+
type: "DataIntegrityProof",
|
|
59
|
+
proofValue: "zNotADocument"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
const before = structuredClone(input);
|
|
64
|
+
const result = discoverCompoundProofDocuments(input, limits);
|
|
65
|
+
assertEquals(input, before);
|
|
66
|
+
assertEquals(result.status, "ok");
|
|
67
|
+
if (result.status !== "ok") return;
|
|
68
|
+
assertEquals(result.documents.map(({ path, id, depth }) => ({
|
|
69
|
+
path,
|
|
70
|
+
id,
|
|
71
|
+
depth
|
|
72
|
+
})), [
|
|
73
|
+
{
|
|
74
|
+
path: "/items/0",
|
|
75
|
+
id: "ap://example.test/objects/2",
|
|
76
|
+
depth: 2
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
path: "/a~1b~0c",
|
|
80
|
+
id: "ap://example.test/objects/3",
|
|
81
|
+
depth: 1
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
path: "/object",
|
|
85
|
+
id: "ap://example.test/objects/1",
|
|
86
|
+
depth: 1
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
path: "",
|
|
90
|
+
id: "ap://example.test/activities/1",
|
|
91
|
+
depth: 0
|
|
92
|
+
}
|
|
93
|
+
]);
|
|
94
|
+
assertEquals(result.documents[0].securedDocument, input.items[0]);
|
|
95
|
+
assertEquals(result.documents[0].proof, input.items[0].proof);
|
|
96
|
+
assertEquals(result.statistics.proofCount, 4);
|
|
97
|
+
input.items[0].content = "mutated after discovery";
|
|
98
|
+
assertEquals(result.documents[0].securedDocument.content, "two");
|
|
99
|
+
assertEquals(result.snapshot.items[0].content, "two");
|
|
100
|
+
});
|
|
101
|
+
test("discoverCompoundProofDocuments() is independent of member order", () => {
|
|
102
|
+
const first = {
|
|
103
|
+
z: {
|
|
104
|
+
id: "z",
|
|
105
|
+
proof: { proofValue: "z" }
|
|
106
|
+
},
|
|
107
|
+
a: {
|
|
108
|
+
id: "a",
|
|
109
|
+
proof: { proofValue: "a" }
|
|
110
|
+
},
|
|
111
|
+
proof: { proofValue: "root" }
|
|
112
|
+
};
|
|
113
|
+
const second = {
|
|
114
|
+
proof: { proofValue: "root" },
|
|
115
|
+
a: {
|
|
116
|
+
proof: { proofValue: "a" },
|
|
117
|
+
id: "a"
|
|
118
|
+
},
|
|
119
|
+
z: {
|
|
120
|
+
proof: { proofValue: "z" },
|
|
121
|
+
id: "z"
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
const firstResult = discoverCompoundProofDocuments(first, limits);
|
|
125
|
+
const secondResult = discoverCompoundProofDocuments(second, limits);
|
|
126
|
+
assertEquals(firstResult.status, "ok");
|
|
127
|
+
assertEquals(secondResult.status, "ok");
|
|
128
|
+
if (firstResult.status !== "ok" || secondResult.status !== "ok") return;
|
|
129
|
+
assertEquals(firstResult.documents.map((candidate) => candidate.path), [
|
|
130
|
+
"/a",
|
|
131
|
+
"/z",
|
|
132
|
+
""
|
|
133
|
+
]);
|
|
134
|
+
assertEquals(secondResult.documents.map((candidate) => candidate.path), [
|
|
135
|
+
"/a",
|
|
136
|
+
"/z",
|
|
137
|
+
""
|
|
138
|
+
]);
|
|
139
|
+
});
|
|
140
|
+
test("discoverCompoundProofDocuments() does not inspect context definitions", () => {
|
|
141
|
+
const result = discoverCompoundProofDocuments({
|
|
142
|
+
"@context": {
|
|
143
|
+
proof: "https://w3id.org/security#proof",
|
|
144
|
+
portable: {
|
|
145
|
+
"@id": "ap://did:key:z6MkContext/objects/1",
|
|
146
|
+
"@context": { proof: "https://example.com/nested-proof" }
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
id: "https://social.example/objects/1",
|
|
150
|
+
type: "Note"
|
|
151
|
+
}, limits);
|
|
152
|
+
assertEquals(result.status, "ok");
|
|
153
|
+
if (result.status !== "ok") return;
|
|
154
|
+
assertEquals(result.documents, []);
|
|
155
|
+
assertEquals(result.statistics.proofCount, 0);
|
|
156
|
+
});
|
|
157
|
+
test("discoverCompoundProofDocuments() preserves the recorded compound vector", () => {
|
|
158
|
+
const result = discoverCompoundProofDocuments(map_local_create_note_default.documents.finalSecuredCompound, limits);
|
|
159
|
+
assertEquals(result.status, "ok");
|
|
160
|
+
if (result.status !== "ok") return;
|
|
161
|
+
assertEquals(result.documents.map(({ path }) => path), ["/object", ""]);
|
|
162
|
+
assertEquals(result.documents[0].securedDocument, map_local_create_note_default.documents.securedInner);
|
|
163
|
+
assertEquals(result.documents[1].securedDocument, map_local_create_note_default.documents.finalSecuredCompound);
|
|
164
|
+
});
|
|
165
|
+
test("discoverCompoundProofDocuments() rejects unsupported proof shapes atomically", () => {
|
|
166
|
+
for (const proof of [
|
|
167
|
+
null,
|
|
168
|
+
"remote-proof",
|
|
169
|
+
[{ proofValue: "z" }]
|
|
170
|
+
]) assertEquals(discoverCompoundProofDocuments({
|
|
171
|
+
object: { proof: { proofValue: "valid candidate" } },
|
|
172
|
+
other: { proof }
|
|
173
|
+
}, limits), {
|
|
174
|
+
status: "unsupported",
|
|
175
|
+
reason: {
|
|
176
|
+
type: "unsupportedProofShape",
|
|
177
|
+
path: "/other/proof"
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
test("discoverCompoundProofDocuments() applies resource limits atomically", () => {
|
|
182
|
+
const cases = [
|
|
183
|
+
{
|
|
184
|
+
input: { child: { child: { proof: {} } } },
|
|
185
|
+
limits: {
|
|
186
|
+
...limits,
|
|
187
|
+
maxDepth: 1
|
|
188
|
+
},
|
|
189
|
+
limit: "depth",
|
|
190
|
+
maximum: 1
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
input: {
|
|
194
|
+
first: {},
|
|
195
|
+
second: { proof: {} }
|
|
196
|
+
},
|
|
197
|
+
limits: {
|
|
198
|
+
...limits,
|
|
199
|
+
maxMaps: 2
|
|
200
|
+
},
|
|
201
|
+
limit: "maps",
|
|
202
|
+
maximum: 2
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
input: {
|
|
206
|
+
first: { proof: {} },
|
|
207
|
+
second: { proof: {} }
|
|
208
|
+
},
|
|
209
|
+
limits: {
|
|
210
|
+
...limits,
|
|
211
|
+
maxProofs: 1
|
|
212
|
+
},
|
|
213
|
+
limit: "proofs",
|
|
214
|
+
maximum: 1
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
input: {
|
|
218
|
+
content: "too large",
|
|
219
|
+
proof: {}
|
|
220
|
+
},
|
|
221
|
+
limits: {
|
|
222
|
+
...limits,
|
|
223
|
+
maxBytes: 8
|
|
224
|
+
},
|
|
225
|
+
limit: "bytes",
|
|
226
|
+
maximum: 8
|
|
227
|
+
}
|
|
228
|
+
];
|
|
229
|
+
for (const fixture of cases) {
|
|
230
|
+
const result = discoverCompoundProofDocuments(fixture.input, fixture.limits);
|
|
231
|
+
assertEquals(result.status, "unsupported");
|
|
232
|
+
if (result.status !== "unsupported") continue;
|
|
233
|
+
assertEquals(result.reason.type, "limitExceeded");
|
|
234
|
+
if (result.reason.type !== "limitExceeded") continue;
|
|
235
|
+
assertEquals(result.reason.limit, fixture.limit);
|
|
236
|
+
assertEquals(result.reason.maximum, fixture.maximum);
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
test("discoverCompoundProofDocuments() rejects values that are not JSON trees", () => {
|
|
240
|
+
const cyclic = { proof: {} };
|
|
241
|
+
cyclic.self = cyclic;
|
|
242
|
+
const shared = { proof: {} };
|
|
243
|
+
const symbolKeyed = { [Symbol("hidden")]: { proof: {} } };
|
|
244
|
+
for (const input of [
|
|
245
|
+
[],
|
|
246
|
+
{ value: void 0 },
|
|
247
|
+
{ value: NaN },
|
|
248
|
+
cyclic,
|
|
249
|
+
{
|
|
250
|
+
first: shared,
|
|
251
|
+
second: shared
|
|
252
|
+
},
|
|
253
|
+
symbolKeyed
|
|
254
|
+
]) {
|
|
255
|
+
const result = discoverCompoundProofDocuments(input, limits);
|
|
256
|
+
assertEquals(result.status, "unsupported");
|
|
257
|
+
if (result.status !== "unsupported") continue;
|
|
258
|
+
assertEquals(result.reason.type, "invalidJsonTree");
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
test("discoverCompoundProofDocuments() handles wide JSON containers", () => {
|
|
262
|
+
const input = { values: new Array(2e5).fill(0) };
|
|
263
|
+
const result = discoverCompoundProofDocuments(input, {
|
|
264
|
+
maxDepth: 2,
|
|
265
|
+
maxMaps: 1,
|
|
266
|
+
maxProofs: 0,
|
|
267
|
+
maxBytes: 1048576
|
|
268
|
+
});
|
|
269
|
+
assertEquals(result.status, "ok");
|
|
270
|
+
if (result.status !== "ok") return;
|
|
271
|
+
assertEquals(result.statistics.byteLength, JSON.stringify(input).length);
|
|
272
|
+
});
|
|
273
|
+
test("discoverCompoundProofDocuments() rejects accessors without invoking them", () => {
|
|
274
|
+
let getterCalls = 0;
|
|
275
|
+
const accessorMap = {};
|
|
276
|
+
Object.defineProperty(accessorMap, "object", {
|
|
277
|
+
enumerable: true,
|
|
278
|
+
get() {
|
|
279
|
+
getterCalls++;
|
|
280
|
+
return { proof: {} };
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
const accessorArray = [{}];
|
|
284
|
+
Object.defineProperty(accessorArray, 0, {
|
|
285
|
+
enumerable: true,
|
|
286
|
+
get() {
|
|
287
|
+
getterCalls++;
|
|
288
|
+
return { proof: {} };
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
for (const input of [accessorMap, { items: accessorArray }]) {
|
|
292
|
+
const result = discoverCompoundProofDocuments(input, limits);
|
|
293
|
+
assertEquals(result.status, "unsupported");
|
|
294
|
+
if (result.status !== "unsupported") continue;
|
|
295
|
+
assertEquals(result.reason.type, "invalidJsonTree");
|
|
296
|
+
}
|
|
297
|
+
assertEquals(getterCalls, 0);
|
|
298
|
+
});
|
|
299
|
+
test("discoverCompoundProofDocuments() rejects non-enumerable map properties", () => {
|
|
300
|
+
const input = {};
|
|
301
|
+
Object.defineProperty(input, "proof", {
|
|
302
|
+
value: {},
|
|
303
|
+
enumerable: false
|
|
304
|
+
});
|
|
305
|
+
const result = discoverCompoundProofDocuments(input, limits);
|
|
306
|
+
assertEquals(result.status, "unsupported");
|
|
307
|
+
if (result.status !== "unsupported") return;
|
|
308
|
+
assertEquals(result.reason.type, "invalidJsonTree");
|
|
309
|
+
});
|
|
310
|
+
test("discoverCompoundProofDocuments() rejects non-JSON array properties", () => {
|
|
311
|
+
const namedProperty = Object.assign([], { extra: { proof: {} } });
|
|
312
|
+
const sparse = new Array(2);
|
|
313
|
+
sparse[1] = null;
|
|
314
|
+
const nonEnumerableIndex = [];
|
|
315
|
+
Object.defineProperty(nonEnumerableIndex, 0, {
|
|
316
|
+
value: { proof: {} },
|
|
317
|
+
enumerable: false
|
|
318
|
+
});
|
|
319
|
+
for (const input of [
|
|
320
|
+
{ items: namedProperty },
|
|
321
|
+
{ items: sparse },
|
|
322
|
+
{ items: nonEnumerableIndex }
|
|
323
|
+
]) {
|
|
324
|
+
const result = discoverCompoundProofDocuments(input, limits);
|
|
325
|
+
assertEquals(result.status, "unsupported");
|
|
326
|
+
if (result.status !== "unsupported") continue;
|
|
327
|
+
assertEquals(result.reason.type, "invalidJsonTree");
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
//#endregion
|
|
331
|
+
export {};
|