@atrib/verify 0.1.0

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 (43) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +177 -0
  3. package/dist/calculate.d.ts +32 -0
  4. package/dist/calculate.d.ts.map +1 -0
  5. package/dist/calculate.js +495 -0
  6. package/dist/calculate.js.map +1 -0
  7. package/dist/graph-fetch.d.ts +22 -0
  8. package/dist/graph-fetch.d.ts.map +1 -0
  9. package/dist/graph-fetch.js +60 -0
  10. package/dist/graph-fetch.js.map +1 -0
  11. package/dist/index.d.ts +15 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +24 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/policy-builder.d.ts +26 -0
  16. package/dist/policy-builder.d.ts.map +1 -0
  17. package/dist/policy-builder.js +49 -0
  18. package/dist/policy-builder.js.map +1 -0
  19. package/dist/recommendation.d.ts +29 -0
  20. package/dist/recommendation.d.ts.map +1 -0
  21. package/dist/recommendation.js +91 -0
  22. package/dist/recommendation.js.map +1 -0
  23. package/dist/resolve-identity.d.ts +84 -0
  24. package/dist/resolve-identity.d.ts.map +1 -0
  25. package/dist/resolve-identity.js +121 -0
  26. package/dist/resolve-identity.js.map +1 -0
  27. package/dist/revocations.d.ts +73 -0
  28. package/dist/revocations.d.ts.map +1 -0
  29. package/dist/revocations.js +83 -0
  30. package/dist/revocations.js.map +1 -0
  31. package/dist/types.d.ts +200 -0
  32. package/dist/types.d.ts.map +1 -0
  33. package/dist/types.js +25 -0
  34. package/dist/types.js.map +1 -0
  35. package/dist/verifier.d.ts +54 -0
  36. package/dist/verifier.d.ts.map +1 -0
  37. package/dist/verifier.js +213 -0
  38. package/dist/verifier.js.map +1 -0
  39. package/dist/verify-record.d.ts +149 -0
  40. package/dist/verify-record.d.ts.map +1 -0
  41. package/dist/verify-record.js +136 -0
  42. package/dist/verify-record.js.map +1 -0
  43. package/package.json +47 -0
package/dist/index.js ADDED
@@ -0,0 +1,24 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // @atrib/verify. Merchant verification library
3
+ // Verifier (primary export)
4
+ export { AtribVerifier } from './verifier.js';
5
+ // Per-record verification (single AtribRecord; surfaces D044 provenance
6
+ // annotations). Distinct from AtribVerifier.verify which operates on a
7
+ // settlement RecommendationDocument. Other per-record annotations the
8
+ // README mentions (informed_by_resolution, capability_check, etc.) are
9
+ // pending; see DECISIONS.md for the planned reconciliation.
10
+ export { verifyRecord } from './verify-record.js';
11
+ // Calculation algorithm (§4.6). pure function, exported for direct use
12
+ export { calculate, DEFAULT_POLICY, isValidPolicy } from './calculate.js';
13
+ // Recommendation document signing/verification (§4.7)
14
+ export { signRecommendation, verifyRecommendationSignature, recommendationSigningInput, distributionsMatch, } from './recommendation.js';
15
+ // Policy builder (compose policies from templates)
16
+ export { buildPolicy, policyFrom } from './policy-builder.js';
17
+ // Graph fetch (advanced usage)
18
+ export { fetchGraph, fetchSessionPolicyRecord, fetchPolicyDocument } from './graph-fetch.js';
19
+ // Revocation registry (§1.9)
20
+ export { buildRevocationRegistry, applyRevocation } from './revocations.js';
21
+ // Identity resolution (§6.3 9-step verifier consultation)
22
+ export { resolveIdentity } from './resolve-identity.js';
23
+ export { graphLabelFromEventTypeUri } from './types.js';
24
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAEtC,+CAA+C;AAE/C,4BAA4B;AAC5B,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAG7C,wEAAwE;AACxE,uEAAuE;AACvE,sEAAsE;AACtE,uEAAuE;AACvE,4DAA4D;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAOjD,uEAAuE;AACvE,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEzE,sDAAsD;AACtD,OAAO,EACL,kBAAkB,EAClB,6BAA6B,EAC7B,0BAA0B,EAC1B,kBAAkB,GACnB,MAAM,qBAAqB,CAAA;AAE5B,mDAAmD;AACnD,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAE7D,+BAA+B;AAC/B,OAAO,EAAE,UAAU,EAAE,wBAAwB,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAA;AAE5F,6BAA6B;AAC7B,OAAO,EAAE,uBAAuB,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAG3E,0DAA0D;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AA+BvD,OAAO,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAA"}
@@ -0,0 +1,26 @@
1
+ import type { PolicyDocument, Modifier, PolicyConstraints } from './types.js';
2
+ type EdgeWeightOverrides = Partial<Record<string, number>>;
3
+ /**
4
+ * Build a policy document by combining a base policy with overrides.
5
+ *
6
+ * - `edge_weights`: merged (override values replace base values per key)
7
+ * - `modifiers`: concatenated (base first, then additions)
8
+ * - `constraints`: merged (override values replace base values per key)
9
+ * - `role`, `distribution`: override replaces base if provided
10
+ *
11
+ * Throws if the resulting policy fails validation.
12
+ */
13
+ export declare function buildPolicy(base: PolicyDocument, overrides: {
14
+ role?: PolicyDocument['role'];
15
+ edge_weights?: EdgeWeightOverrides;
16
+ modifiers?: Modifier[];
17
+ constraints?: Partial<PolicyConstraints>;
18
+ distribution?: PolicyDocument['distribution'];
19
+ }): PolicyDocument;
20
+ /**
21
+ * Start from the default policy and apply overrides.
22
+ * Shorthand for `buildPolicy(DEFAULT_POLICY, overrides)`.
23
+ */
24
+ export declare function policyFrom(overrides: Parameters<typeof buildPolicy>[1]): PolicyDocument;
25
+ export {};
26
+ //# sourceMappingURL=policy-builder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy-builder.d.ts","sourceRoot":"","sources":["../src/policy-builder.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAE7E,KAAK,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;AAE1D;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,cAAc,EACpB,SAAS,EAAE;IACT,IAAI,CAAC,EAAE,cAAc,CAAC,MAAM,CAAC,CAAA;IAC7B,YAAY,CAAC,EAAE,mBAAmB,CAAA;IAClC,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAA;IACtB,WAAW,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAA;IACxC,YAAY,CAAC,EAAE,cAAc,CAAC,cAAc,CAAC,CAAA;CAC9C,GACA,cAAc,CAqBhB;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,SAAS,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC,GAAG,cAAc,CAEvF"}
@@ -0,0 +1,49 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ /**
3
+ * Policy builder. helper for composing policy documents from templates.
4
+ *
5
+ * Combines a base policy with overrides for edge weights, modifiers,
6
+ * and constraints. Fields from the override replace fields in the base;
7
+ * modifiers are concatenated (base modifiers first, then override modifiers).
8
+ */
9
+ import { DEFAULT_POLICY, isValidPolicy } from './calculate.js';
10
+ /**
11
+ * Build a policy document by combining a base policy with overrides.
12
+ *
13
+ * - `edge_weights`: merged (override values replace base values per key)
14
+ * - `modifiers`: concatenated (base first, then additions)
15
+ * - `constraints`: merged (override values replace base values per key)
16
+ * - `role`, `distribution`: override replaces base if provided
17
+ *
18
+ * Throws if the resulting policy fails validation.
19
+ */
20
+ export function buildPolicy(base, overrides) {
21
+ const result = {
22
+ spec_version: 'atrib/1.0',
23
+ edge_weights: {
24
+ ...base.edge_weights,
25
+ ...overrides.edge_weights,
26
+ },
27
+ modifiers: [...(base.modifiers ?? []), ...(overrides.modifiers ?? [])],
28
+ constraints: {
29
+ ...base.constraints,
30
+ ...overrides.constraints,
31
+ },
32
+ };
33
+ if (overrides.role ?? base.role)
34
+ result.role = overrides.role ?? base.role;
35
+ if (overrides.distribution ?? base.distribution)
36
+ result.distribution = overrides.distribution ?? base.distribution;
37
+ if (!isValidPolicy(result)) {
38
+ throw new Error('buildPolicy: resulting policy is invalid (check weights, constraints)');
39
+ }
40
+ return result;
41
+ }
42
+ /**
43
+ * Start from the default policy and apply overrides.
44
+ * Shorthand for `buildPolicy(DEFAULT_POLICY, overrides)`.
45
+ */
46
+ export function policyFrom(overrides) {
47
+ return buildPolicy(DEFAULT_POLICY, overrides);
48
+ }
49
+ //# sourceMappingURL=policy-builder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy-builder.js","sourceRoot":"","sources":["../src/policy-builder.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAEtC;;;;;;GAMG;AAEH,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAK9D;;;;;;;;;GASG;AACH,MAAM,UAAU,WAAW,CACzB,IAAoB,EACpB,SAMC;IAED,MAAM,MAAM,GAA4B;QACtC,YAAY,EAAE,WAAW;QACzB,YAAY,EAAE;YACZ,GAAG,IAAI,CAAC,YAAY;YACpB,GAAG,SAAS,CAAC,YAAY;SAC1B;QACD,SAAS,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,SAAS,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;QACtE,WAAW,EAAE;YACX,GAAG,IAAI,CAAC,WAAW;YACnB,GAAG,SAAS,CAAC,WAAW;SACzB;KACF,CAAA;IACD,IAAI,SAAS,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI;QAAE,MAAM,CAAC,IAAI,GAAG,SAAS,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,CAAA;IAC1E,IAAI,SAAS,CAAC,YAAY,IAAI,IAAI,CAAC,YAAY;QAAE,MAAM,CAAC,YAAY,GAAG,SAAS,CAAC,YAAY,IAAI,IAAI,CAAC,YAAY,CAAA;IAElH,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,uEAAuE,CAAC,CAAA;IAC1F,CAAC;IAED,OAAO,MAAwB,CAAA;AACjC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,SAA4C;IACrE,OAAO,WAAW,CAAC,cAAc,EAAE,SAAS,CAAC,CAAA;AAC/C,CAAC"}
@@ -0,0 +1,29 @@
1
+ import type { RecommendationDocument } from './types.js';
2
+ /**
3
+ * Compute the canonical signing input for a recommendation: JCS-canonicalize
4
+ * the document with the `signature` field omitted.
5
+ */
6
+ export declare function recommendationSigningInput(doc: Omit<RecommendationDocument, 'signature'> & {
7
+ signature?: string;
8
+ }): Uint8Array;
9
+ /**
10
+ * Sign a recommendation document with a 32-byte Ed25519 seed.
11
+ * Returns a copy of the document with the `signature` field populated.
12
+ */
13
+ export declare function signRecommendation(doc: Omit<RecommendationDocument, 'signature'>, privateKey: Uint8Array): Promise<RecommendationDocument>;
14
+ /**
15
+ * Verify the Ed25519 signature on a recommendation document.
16
+ *
17
+ * @param doc. the document with `signature` populated
18
+ * @param publicKey. base64url-encoded 32-byte public key of `calculated_by`
19
+ * @returns true iff the signature is valid
20
+ */
21
+ export declare function verifyRecommendationSignature(doc: RecommendationDocument, publicKey: string): Promise<boolean>;
22
+ /**
23
+ * Compare two distributions for equality within the §4.7.3 tolerance of 1e-9.
24
+ *
25
+ * Returns true iff both have identical keys AND every value matches within
26
+ * the tolerance.
27
+ */
28
+ export declare function distributionsMatch(a: Record<string, number>, b: Record<string, number>, tolerance?: number): boolean;
29
+ //# sourceMappingURL=recommendation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recommendation.d.ts","sourceRoot":"","sources":["../src/recommendation.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAA;AAMxD;;;GAGG;AACH,wBAAgB,0BAA0B,CACxC,GAAG,EAAE,IAAI,CAAC,sBAAsB,EAAE,WAAW,CAAC,GAAG;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GACtE,UAAU,CAQZ;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,IAAI,CAAC,sBAAsB,EAAE,WAAW,CAAC,EAC9C,UAAU,EAAE,UAAU,GACrB,OAAO,CAAC,sBAAsB,CAAC,CAUjC;AAED;;;;;;GAMG;AACH,wBAAsB,6BAA6B,CACjD,GAAG,EAAE,sBAAsB,EAC3B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC,CAYlB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACzB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACzB,SAAS,SAAO,GACf,OAAO,CAcT"}
@@ -0,0 +1,91 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ /**
3
+ * Settlement recommendation document signing & verification (§4.7).
4
+ *
5
+ * Uses the same Ed25519 + JCS canonicalization machinery as @atrib/mcp.
6
+ */
7
+ import * as ed from '@noble/ed25519';
8
+ import { sha512 } from '@noble/hashes/sha2.js';
9
+ import canonicalize from 'canonicalize';
10
+ import { base64urlEncode, base64urlDecode } from '@atrib/mcp';
11
+ // @noble/ed25519 v2 requires setting the sha512 sync function (same as @atrib/mcp)
12
+ ed.etc.sha512Sync = (...m) => sha512(ed.etc.concatBytes(...m));
13
+ ed.etc.sha512Async = async (...m) => sha512(ed.etc.concatBytes(...m));
14
+ /**
15
+ * Compute the canonical signing input for a recommendation: JCS-canonicalize
16
+ * the document with the `signature` field omitted.
17
+ */
18
+ export function recommendationSigningInput(doc) {
19
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
20
+ const { signature: _sig, ...rest } = doc;
21
+ const canonical = canonicalize(rest);
22
+ if (!canonical) {
23
+ throw new Error('failed to canonicalize recommendation document');
24
+ }
25
+ return new TextEncoder().encode(canonical);
26
+ }
27
+ /**
28
+ * Sign a recommendation document with a 32-byte Ed25519 seed.
29
+ * Returns a copy of the document with the `signature` field populated.
30
+ */
31
+ export async function signRecommendation(doc, privateKey) {
32
+ if (privateKey.length !== 32) {
33
+ throw new Error('privateKey must be 32 bytes');
34
+ }
35
+ const message = recommendationSigningInput(doc);
36
+ const sigBytes = await ed.signAsync(message, privateKey);
37
+ return {
38
+ ...doc,
39
+ signature: base64urlEncode(sigBytes),
40
+ };
41
+ }
42
+ /**
43
+ * Verify the Ed25519 signature on a recommendation document.
44
+ *
45
+ * @param doc. the document with `signature` populated
46
+ * @param publicKey. base64url-encoded 32-byte public key of `calculated_by`
47
+ * @returns true iff the signature is valid
48
+ */
49
+ export async function verifyRecommendationSignature(doc, publicKey) {
50
+ try {
51
+ if (!doc.signature)
52
+ return false;
53
+ const pubBytes = base64urlDecode(publicKey);
54
+ if (pubBytes.length !== 32)
55
+ return false;
56
+ const sigBytes = base64urlDecode(doc.signature);
57
+ if (sigBytes.length !== 64)
58
+ return false;
59
+ const message = recommendationSigningInput(doc);
60
+ return await ed.verifyAsync(sigBytes, message, pubBytes);
61
+ }
62
+ catch {
63
+ return false;
64
+ }
65
+ }
66
+ /**
67
+ * Compare two distributions for equality within the §4.7.3 tolerance of 1e-9.
68
+ *
69
+ * Returns true iff both have identical keys AND every value matches within
70
+ * the tolerance.
71
+ */
72
+ export function distributionsMatch(a, b, tolerance = 1e-9) {
73
+ const aKeys = Object.keys(a).sort();
74
+ const bKeys = Object.keys(b).sort();
75
+ if (aKeys.length !== bKeys.length)
76
+ return false;
77
+ for (let i = 0; i < aKeys.length; i++) {
78
+ if (aKeys[i] !== bKeys[i])
79
+ return false;
80
+ const av = a[aKeys[i]];
81
+ const bv = b[bKeys[i]];
82
+ // NaN never matches anything (including itself). calculations producing
83
+ // NaN are bugs and must surface as verification failures.
84
+ if (Number.isNaN(av) || Number.isNaN(bv))
85
+ return false;
86
+ if (Math.abs(av - bv) > tolerance)
87
+ return false;
88
+ }
89
+ return true;
90
+ }
91
+ //# sourceMappingURL=recommendation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recommendation.js","sourceRoot":"","sources":["../src/recommendation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Identity resolution per spec §6.3 — the 9-step verifier consultation
3
+ * algorithm. The verifier consults the directory for a record's
4
+ * creator_key and surfaces an `identity_resolution` object.
5
+ *
6
+ * This implementation is the LEGIBLE PARTIAL: steps 6 (directory
7
+ * lookup), 8 (parse claim), and 9 (revocation cross-check) are wired
8
+ * end-to-end. Steps 1-5 (anchor freshness, witness coverage, directory
9
+ * checkpoint signature, append-only consistency) and step 7 (AKD proof
10
+ * validation) require heavier cryptographic infrastructure and are
11
+ * surfaced as explicit warnings rather than falsely claimed as passing.
12
+ *
13
+ * Per §5.8 degradation contract: this function never throws. Network
14
+ * failures, malformed responses, and timeout conditions all produce
15
+ * warnings in the returned object.
16
+ */
17
+ import type { RevocationEntry } from './revocations.js';
18
+ /** Identity claim shape from spec §6.1. Mirrored here to avoid an import cycle with @atrib/directory. */
19
+ export interface IdentityClaim {
20
+ creator_key: string;
21
+ claim_type: 'self_attested' | 'domain_verified' | 'did_resolved';
22
+ claim_method: string;
23
+ claim_subject: Record<string, unknown>;
24
+ capabilities?: CapabilityEnvelope;
25
+ signature: string;
26
+ }
27
+ /** Capability envelope per §6.7 / D051. */
28
+ export interface CapabilityEnvelope {
29
+ tool_names?: string[];
30
+ max_amount?: {
31
+ currency: string;
32
+ value: number;
33
+ };
34
+ counterparties?: string[];
35
+ event_types?: string[];
36
+ expires_at?: number;
37
+ }
38
+ export type IdentityResolutionMethod = 'directory_lookup' | 'no_anchor_available' | 'no_claim_registered' | 'rejected';
39
+ export interface KeyRevocationStatus {
40
+ reason: 'rotation' | 'retirement' | 'compromise';
41
+ /** Log index of the revocation, used to derive timestamp ordering. */
42
+ revoked_at_log_index: number;
43
+ /** True when the record being resolved was signed AFTER revocation (post-revocation). */
44
+ since_revocation: boolean;
45
+ }
46
+ export interface IdentityResolution {
47
+ identity_resolved: IdentityClaim | null;
48
+ identity_resolution_method: IdentityResolutionMethod;
49
+ capability_envelope: CapabilityEnvelope | null;
50
+ key_revocation_status: KeyRevocationStatus | null;
51
+ /**
52
+ * lookup_proof_valid: per §6.3 step 7. NULL when the lookup returned
53
+ * non-membership (no proof to validate) or when the verifier hasn't
54
+ * implemented AKD proof validation in JS. The directory returns a
55
+ * proof bytes blob; validation requires the AKD WASM bridge in the
56
+ * verifier process. Currently null for both reasons; warnings flag
57
+ * which one applies.
58
+ */
59
+ lookup_proof_valid: boolean | null;
60
+ /** §6.3 step 5 — currently not checked; reflected in warnings. */
61
+ append_only_consistent: boolean | null;
62
+ /** §6.3 step 1 — currently not checked; reflected in warnings. */
63
+ anchor: null;
64
+ warnings: string[];
65
+ }
66
+ export interface ResolveIdentityOptions {
67
+ /** Directory base URL (e.g., https://directory.atrib.dev/v6). */
68
+ directoryEndpoint?: string;
69
+ /** Record timestamp (ms) — for the revocation since/before comparison. */
70
+ recordTimestamp?: number;
71
+ /**
72
+ * Record's log_index — for revocation ordering per §1.9.3 since the
73
+ * authoritative ordering is by log_index, not timestamp.
74
+ */
75
+ recordLogIndex?: number | null;
76
+ /** Pre-built revocation registry from a log scan (recommended). */
77
+ revocations?: Map<string, RevocationEntry>;
78
+ /** AbortSignal for timeout / cancellation. */
79
+ signal?: AbortSignal;
80
+ /** Override fetch (testing). */
81
+ fetchImpl?: typeof fetch;
82
+ }
83
+ export declare function resolveIdentity(creatorKey: string, opts?: ResolveIdentityOptions): Promise<IdentityResolution>;
84
+ //# sourceMappingURL=resolve-identity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-identity.d.ts","sourceRoot":"","sources":["../src/resolve-identity.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAEvD,yGAAyG;AACzG,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAA;IACnB,UAAU,EAAE,eAAe,GAAG,iBAAiB,GAAG,cAAc,CAAA;IAChE,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACtC,YAAY,CAAC,EAAE,kBAAkB,CAAA;IACjC,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,2CAA2C;AAC3C,MAAM,WAAW,kBAAkB;IACjC,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;IACrB,UAAU,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;IAChD,cAAc,CAAC,EAAE,MAAM,EAAE,CAAA;IACzB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;IACtB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,MAAM,wBAAwB,GAChC,kBAAkB,GAClB,qBAAqB,GACrB,qBAAqB,GACrB,UAAU,CAAA;AAEd,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,UAAU,GAAG,YAAY,GAAG,YAAY,CAAA;IAChD,sEAAsE;IACtE,oBAAoB,EAAE,MAAM,CAAA;IAC5B,yFAAyF;IACzF,gBAAgB,EAAE,OAAO,CAAA;CAC1B;AAED,MAAM,WAAW,kBAAkB;IACjC,iBAAiB,EAAE,aAAa,GAAG,IAAI,CAAA;IACvC,0BAA0B,EAAE,wBAAwB,CAAA;IACpD,mBAAmB,EAAE,kBAAkB,GAAG,IAAI,CAAA;IAC9C,qBAAqB,EAAE,mBAAmB,GAAG,IAAI,CAAA;IACjD;;;;;;;OAOG;IACH,kBAAkB,EAAE,OAAO,GAAG,IAAI,CAAA;IAClC,kEAAkE;IAClE,sBAAsB,EAAE,OAAO,GAAG,IAAI,CAAA;IACtC,kEAAkE;IAClE,MAAM,EAAE,IAAI,CAAA;IACZ,QAAQ,EAAE,MAAM,EAAE,CAAA;CACnB;AAED,MAAM,WAAW,sBAAsB;IACrC,iEAAiE;IACjE,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,0EAA0E;IAC1E,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,mEAAmE;IACnE,WAAW,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAA;IAC1C,8CAA8C;IAC9C,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,gCAAgC;IAChC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;CACzB;AAID,wBAAsB,eAAe,CACnC,UAAU,EAAE,MAAM,EAClB,IAAI,GAAE,sBAA2B,GAChC,OAAO,CAAC,kBAAkB,CAAC,CAmG7B"}
@@ -0,0 +1,121 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ const DEFAULT_DIRECTORY = 'https://directory.atrib.dev/v6';
3
+ export async function resolveIdentity(creatorKey, opts = {}) {
4
+ const warnings = [];
5
+ const directoryEndpoint = opts.directoryEndpoint ?? DEFAULT_DIRECTORY;
6
+ const fetchFn = opts.fetchImpl ?? fetch;
7
+ // Steps not yet implemented — flag once, up front, so consumers can
8
+ // distinguish "verifier didn't check this" from "verifier checked
9
+ // this and it passed."
10
+ warnings.push('step-1-anchor-not-checked: anchor freshness not verified by this implementation');
11
+ warnings.push('step-3-witness-not-checked: witness coverage not verified by this implementation');
12
+ warnings.push('step-4-checkpoint-signature-not-checked: directory checkpoint signature not verified by this implementation');
13
+ warnings.push('step-5-append-only-not-checked: append-only consistency not verified by this implementation');
14
+ warnings.push('step-7-akd-proof-not-validated: AKD lookup proof returned but not cryptographically validated');
15
+ // Step 6: directory lookup
16
+ let lookupBody = {};
17
+ try {
18
+ const url = `${directoryEndpoint.replace(/\/$/, '')}/lookup/${encodeURIComponent(creatorKey)}`;
19
+ const res = await fetchFn(url, {
20
+ headers: { accept: 'application/json' },
21
+ ...(opts.signal ? { signal: opts.signal } : {}),
22
+ });
23
+ if (res.status === 404) {
24
+ // Non-membership. The endpoint returns { found: false } body.
25
+ lookupBody = { found: false };
26
+ }
27
+ else if (!res.ok) {
28
+ warnings.push(`step-6-directory-error: ${res.status} ${res.statusText}`);
29
+ const status = applyRevocationOnly(creatorKey, opts, warnings);
30
+ return {
31
+ identity_resolved: null,
32
+ identity_resolution_method: 'rejected',
33
+ capability_envelope: null,
34
+ key_revocation_status: status,
35
+ lookup_proof_valid: null,
36
+ append_only_consistent: null,
37
+ anchor: null,
38
+ warnings,
39
+ };
40
+ }
41
+ else {
42
+ lookupBody = await res.json();
43
+ }
44
+ }
45
+ catch (e) {
46
+ const msg = e instanceof Error ? e.message : String(e);
47
+ warnings.push(`step-6-directory-error: ${msg}`);
48
+ const status = applyRevocationOnly(creatorKey, opts, warnings);
49
+ return {
50
+ identity_resolved: null,
51
+ identity_resolution_method: 'rejected',
52
+ capability_envelope: null,
53
+ key_revocation_status: status,
54
+ lookup_proof_valid: null,
55
+ append_only_consistent: null,
56
+ anchor: null,
57
+ warnings,
58
+ };
59
+ }
60
+ // Non-membership branch
61
+ if (lookupBody.found === false) {
62
+ return {
63
+ identity_resolved: null,
64
+ identity_resolution_method: 'no_claim_registered',
65
+ capability_envelope: null,
66
+ key_revocation_status: applyRevocationOnly(creatorKey, opts, warnings),
67
+ lookup_proof_valid: null,
68
+ append_only_consistent: null,
69
+ anchor: null,
70
+ warnings,
71
+ };
72
+ }
73
+ // Step 8: parse claim. The directory already returned a parsed JSON
74
+ // claim object; spec-conformance check is delegated to the schema
75
+ // already applied by the directory. We do a minimal sanity check.
76
+ const claim = lookupBody.claim;
77
+ if (!claim || typeof claim !== 'object' || claim.creator_key !== creatorKey) {
78
+ warnings.push('step-8-claim-malformed: lookup returned but claim payload is missing or wrong creator_key');
79
+ return {
80
+ identity_resolved: null,
81
+ identity_resolution_method: 'rejected',
82
+ capability_envelope: null,
83
+ key_revocation_status: applyRevocationOnly(creatorKey, opts, warnings),
84
+ lookup_proof_valid: null,
85
+ append_only_consistent: null,
86
+ anchor: null,
87
+ warnings,
88
+ };
89
+ }
90
+ return {
91
+ identity_resolved: claim,
92
+ identity_resolution_method: 'directory_lookup',
93
+ capability_envelope: claim.capabilities ?? null,
94
+ key_revocation_status: applyRevocationOnly(creatorKey, opts, warnings),
95
+ lookup_proof_valid: null,
96
+ append_only_consistent: null,
97
+ anchor: null,
98
+ warnings,
99
+ };
100
+ }
101
+ /**
102
+ * Step 9 helper: cross-check the revocation registry. since_revocation
103
+ * is true when the record's log_index is strictly greater than the
104
+ * revocation's log_index.
105
+ */
106
+ function applyRevocationOnly(creatorKey, opts, warnings) {
107
+ if (!opts.revocations) {
108
+ warnings.push('step-9-revocation-not-checked: no revocation registry supplied');
109
+ return null;
110
+ }
111
+ const entry = opts.revocations.get(creatorKey);
112
+ if (!entry)
113
+ return null;
114
+ const sinceRevocation = typeof opts.recordLogIndex === 'number' && opts.recordLogIndex > entry.log_index;
115
+ return {
116
+ reason: entry.revocation_reason,
117
+ revoked_at_log_index: entry.log_index,
118
+ since_revocation: sinceRevocation,
119
+ };
120
+ }
121
+ //# sourceMappingURL=resolve-identity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-identity.js","sourceRoot":"","sources":["../src/resolve-identity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Revocation registry per spec §1.9.
3
+ *
4
+ * A "key_revocation" record retires a creator_key at a specific log_index.
5
+ * Records signed by that key with log_index >= R are flagged
6
+ * 'revoked_after_revocation' and MUST NOT contribute to attribution
7
+ * calculations (§4.6). Records with log_index < R retain their state.
8
+ *
9
+ * Why a registry: per-record verification needs to know "was this signer
10
+ * retired before this record was committed?" The answer requires knowing
11
+ * about every key_revocation in the log. Build the registry once over the
12
+ * full log; consult it per record.
13
+ */
14
+ import type { VerificationState } from './types.js';
15
+ /** Reasons per §1.9.1. */
16
+ export type RevocationReason = 'rotation' | 'retirement' | 'compromise';
17
+ /** A single key_revocation event extracted from a log entry. */
18
+ export interface RevocationEntry {
19
+ /** Base64url Ed25519 pubkey being retired. */
20
+ revoked_key: string;
21
+ /** Log index at which the revocation was committed. Records with log_index >= R for revoked_key are post-revocation. */
22
+ log_index: number;
23
+ /** Reason per §1.9.1. */
24
+ revocation_reason: RevocationReason;
25
+ /** Successor key when revocation_reason='rotation'. */
26
+ successor_key?: string;
27
+ /** Emergency-key signer when revocation_reason='compromise' AND signed by an emergency key. */
28
+ emergency_signed_by?: string;
29
+ }
30
+ /**
31
+ * The minimal record shape this module needs. Avoids depending on the
32
+ * full @atrib/mcp record type so callers in graph-node + verify can
33
+ * consume their own decoded forms.
34
+ */
35
+ export interface MinimalRecord {
36
+ event_type?: string | null;
37
+ event_type_uri?: string | null;
38
+ creator_key?: string | null;
39
+ log_index?: number | null;
40
+ revoked_key?: string;
41
+ revocation_reason?: string;
42
+ successor_key?: string;
43
+ emergency_signed_by?: string;
44
+ }
45
+ /**
46
+ * Scan a list of records for valid key_revocation entries and return a
47
+ * registry indexed by revoked_key. When the same key is revoked more
48
+ * than once (which the spec doesn't strictly forbid but is unusual),
49
+ * the EARLIEST revocation wins — once retired, a key cannot be
50
+ * un-retired by a later record.
51
+ *
52
+ * Records that look like revocations but lack required fields, or have
53
+ * non-numeric log_index, are skipped silently. Cryptographic validity
54
+ * (signature checks per §1.9.2 signing rules) is the caller's job —
55
+ * this module only registers what the records claim.
56
+ */
57
+ export declare function buildRevocationRegistry(records: MinimalRecord[]): Map<string, RevocationEntry>;
58
+ /**
59
+ * Apply a revocation registry to a node's verification state. Returns
60
+ * the new state. Per §1.9.3:
61
+ * - creator_key revoked AND log_index >= revocation log_index
62
+ * → 'revoked_after_revocation'
63
+ * - otherwise → unchanged
64
+ *
65
+ * The current state ('signature_valid', 'log_committed', etc.) is
66
+ * preserved for pre-revocation records — those remain valid.
67
+ */
68
+ export declare function applyRevocation(node: {
69
+ creator_key: string | null;
70
+ log_index: number | null;
71
+ verification_state: VerificationState;
72
+ }, registry: Map<string, RevocationEntry>): VerificationState;
73
+ //# sourceMappingURL=revocations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"revocations.d.ts","sourceRoot":"","sources":["../src/revocations.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAEnD,0BAA0B;AAC1B,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,YAAY,GAAG,YAAY,CAAA;AAEvE,gEAAgE;AAChE,MAAM,WAAW,eAAe;IAC9B,8CAA8C;IAC9C,WAAW,EAAE,MAAM,CAAA;IACnB,wHAAwH;IACxH,SAAS,EAAE,MAAM,CAAA;IACjB,yBAAyB;IACzB,iBAAiB,EAAE,gBAAgB,CAAA;IACnC,uDAAuD;IACvD,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,+FAA+F;IAC/F,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAC7B;AAED;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAC7B;AAeD;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,aAAa,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAmB9F;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE;IAAE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,kBAAkB,EAAE,iBAAiB,CAAA;CAAE,EACrG,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,GACrC,iBAAiB,CAcnB"}
@@ -0,0 +1,83 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ const KEY_REVOCATION_URI = 'https://atrib.dev/v1/types/key_revocation';
3
+ function isRevocationRecord(r) {
4
+ // The record may carry the type as either the short label ('key_revocation'),
5
+ // the canonical URI ('https://atrib.dev/v1/types/key_revocation'), or via
6
+ // a separate event_type_uri field (e.g., when the source surfaces both
7
+ // a short label in event_type and the full URI alongside).
8
+ if (r.event_type === 'key_revocation')
9
+ return true;
10
+ if (r.event_type === KEY_REVOCATION_URI)
11
+ return true;
12
+ if (r.event_type_uri === KEY_REVOCATION_URI)
13
+ return true;
14
+ return false;
15
+ }
16
+ /**
17
+ * Scan a list of records for valid key_revocation entries and return a
18
+ * registry indexed by revoked_key. When the same key is revoked more
19
+ * than once (which the spec doesn't strictly forbid but is unusual),
20
+ * the EARLIEST revocation wins — once retired, a key cannot be
21
+ * un-retired by a later record.
22
+ *
23
+ * Records that look like revocations but lack required fields, or have
24
+ * non-numeric log_index, are skipped silently. Cryptographic validity
25
+ * (signature checks per §1.9.2 signing rules) is the caller's job —
26
+ * this module only registers what the records claim.
27
+ */
28
+ export function buildRevocationRegistry(records) {
29
+ const registry = new Map();
30
+ for (const r of records) {
31
+ if (!isRevocationRecord(r))
32
+ continue;
33
+ if (typeof r.revoked_key !== 'string' || r.revoked_key.length === 0)
34
+ continue;
35
+ if (typeof r.log_index !== 'number')
36
+ continue;
37
+ const reason = r.revocation_reason;
38
+ if (reason !== 'rotation' && reason !== 'retirement' && reason !== 'compromise')
39
+ continue;
40
+ const existing = registry.get(r.revoked_key);
41
+ if (existing && existing.log_index <= r.log_index)
42
+ continue; // earlier revocation wins
43
+ registry.set(r.revoked_key, {
44
+ revoked_key: r.revoked_key,
45
+ log_index: r.log_index,
46
+ revocation_reason: reason,
47
+ ...(r.successor_key ? { successor_key: r.successor_key } : {}),
48
+ ...(r.emergency_signed_by ? { emergency_signed_by: r.emergency_signed_by } : {}),
49
+ });
50
+ }
51
+ return registry;
52
+ }
53
+ /**
54
+ * Apply a revocation registry to a node's verification state. Returns
55
+ * the new state. Per §1.9.3:
56
+ * - creator_key revoked AND log_index >= revocation log_index
57
+ * → 'revoked_after_revocation'
58
+ * - otherwise → unchanged
59
+ *
60
+ * The current state ('signature_valid', 'log_committed', etc.) is
61
+ * preserved for pre-revocation records — those remain valid.
62
+ */
63
+ export function applyRevocation(node, registry) {
64
+ if (!node.creator_key)
65
+ return node.verification_state;
66
+ if (node.log_index === null)
67
+ return node.verification_state;
68
+ const entry = registry.get(node.creator_key);
69
+ if (!entry)
70
+ return node.verification_state;
71
+ if (node.log_index < entry.log_index)
72
+ return node.verification_state;
73
+ // The revocation record itself is the one at log_index === entry.log_index;
74
+ // it does NOT flag itself as revoked (it's the act of retirement, not a
75
+ // post-retirement record). Spec §1.9.3 says "log_index >= R" but the
76
+ // implicit reading is "for OTHER records signed by revoked_key" — the
77
+ // revocation record's own state is unaffected. To be safe + spec-aligned,
78
+ // we apply only when this is a strictly post-revocation record.
79
+ if (node.log_index === entry.log_index)
80
+ return node.verification_state;
81
+ return 'revoked_after_revocation';
82
+ }
83
+ //# sourceMappingURL=revocations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"revocations.js","sourceRoot":"","sources":["../src/revocations.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAmDtC,MAAM,kBAAkB,GAAG,2CAA2C,CAAA;AAEtE,SAAS,kBAAkB,CAAC,CAAgB;IAC1C,8EAA8E;IAC9E,0EAA0E;IAC1E,uEAAuE;IACvE,2DAA2D;IAC3D,IAAI,CAAC,CAAC,UAAU,KAAK,gBAAgB;QAAE,OAAO,IAAI,CAAA;IAClD,IAAI,CAAC,CAAC,UAAU,KAAK,kBAAkB;QAAE,OAAO,IAAI,CAAA;IACpD,IAAI,CAAC,CAAC,cAAc,KAAK,kBAAkB;QAAE,OAAO,IAAI,CAAA;IACxD,OAAO,KAAK,CAAA;AACd,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,uBAAuB,CAAC,OAAwB;IAC9D,MAAM,QAAQ,GAAG,IAAI,GAAG,EAA2B,CAAA;IACnD,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,IAAI,CAAC,kBAAkB,CAAC,CAAC,CAAC;YAAE,SAAQ;QACpC,IAAI,OAAO,CAAC,CAAC,WAAW,KAAK,QAAQ,IAAI,CAAC,CAAC,WAAW,CAAC,MAAM,KAAK,CAAC;YAAE,SAAQ;QAC7E,IAAI,OAAO,CAAC,CAAC,SAAS,KAAK,QAAQ;YAAE,SAAQ;QAC7C,MAAM,MAAM,GAAG,CAAC,CAAC,iBAAiB,CAAA;QAClC,IAAI,MAAM,KAAK,UAAU,IAAI,MAAM,KAAK,YAAY,IAAI,MAAM,KAAK,YAAY;YAAE,SAAQ;QACzF,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,CAAC,CAAA;QAC5C,IAAI,QAAQ,IAAI,QAAQ,CAAC,SAAS,IAAI,CAAC,CAAC,SAAS;YAAE,SAAQ,CAAC,0BAA0B;QACtF,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,EAAE;YAC1B,WAAW,EAAE,CAAC,CAAC,WAAW;YAC1B,SAAS,EAAE,CAAC,CAAC,SAAS;YACtB,iBAAiB,EAAE,MAAM;YACzB,GAAG,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC9D,GAAG,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,EAAE,mBAAmB,EAAE,CAAC,CAAC,mBAAmB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACjF,CAAC,CAAA;IACJ,CAAC;IACD,OAAO,QAAQ,CAAA;AACjB,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,eAAe,CAC7B,IAAqG,EACrG,QAAsC;IAEtC,IAAI,CAAC,IAAI,CAAC,WAAW;QAAE,OAAO,IAAI,CAAC,kBAAkB,CAAA;IACrD,IAAI,IAAI,CAAC,SAAS,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC,kBAAkB,CAAA;IAC3D,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;IAC5C,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC,kBAAkB,CAAA;IAC1C,IAAI,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC,SAAS;QAAE,OAAO,IAAI,CAAC,kBAAkB,CAAA;IACpE,4EAA4E;IAC5E,wEAAwE;IACxE,qEAAqE;IACrE,sEAAsE;IACtE,0EAA0E;IAC1E,gEAAgE;IAChE,IAAI,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC,SAAS;QAAE,OAAO,IAAI,CAAC,kBAAkB,CAAA;IACtE,OAAO,0BAA0B,CAAA;AACnC,CAAC"}