@keytrace/claims 0.0.5

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/src/verify.ts ADDED
@@ -0,0 +1,167 @@
1
+ import type { ClaimRecord, ClaimVerificationResult, ES256PublicJwk, KeyRecord, SignedClaimData, VerificationResult, VerificationStep, VerifyOptions } from "./types.js";
2
+ import { resolveHandle, resolvePds, listClaimRecords, getRecordByUri } from "./atproto.js";
3
+ import { verifyES256Signature } from "./crypto/signature.js";
4
+
5
+ // Re-export types for convenience
6
+ export type {
7
+ ClaimIdentity,
8
+ ClaimRecord,
9
+ ClaimSignature,
10
+ ClaimVerificationResult,
11
+ ES256PublicJwk,
12
+ KeyRecord,
13
+ SignedClaimData,
14
+ VerificationResult,
15
+ VerificationStep,
16
+ VerifyOptions,
17
+ } from "./types.js";
18
+
19
+ /**
20
+ * Verify all keytrace claims for a handle.
21
+ *
22
+ * @param handle The ATProto handle (e.g., "alice.bsky.social") or DID
23
+ * @param options Optional configuration
24
+ * @returns Verification results for all claims
25
+ */
26
+ export async function getClaimsForHandle(handle: string, options?: VerifyOptions): Promise<VerificationResult> {
27
+ const did = await resolveHandle(handle, options);
28
+ const result = await getClaimsForDid(did, options);
29
+
30
+ return {
31
+ ...result,
32
+ handle: handle.startsWith("did:") ? undefined : handle,
33
+ };
34
+ }
35
+
36
+ /**
37
+ * Verify all keytrace claims for a DID.
38
+ *
39
+ * @param did The ATProto DID (e.g., "did:plc:abc123")
40
+ * @param options Optional configuration
41
+ * @returns Verification results for all claims
42
+ */
43
+ export async function getClaimsForDid(did: string, options?: VerifyOptions): Promise<VerificationResult> {
44
+ // Resolve PDS for the user
45
+ const pdsUrl = await resolvePds(did, options);
46
+
47
+ // Fetch all claim records
48
+ let claimRecords: Array<{ uri: string; rkey: string; value: ClaimRecord }>;
49
+ try {
50
+ claimRecords = await listClaimRecords(pdsUrl, did, options);
51
+ } catch {
52
+ // No claims found
53
+ return {
54
+ did,
55
+ claims: [],
56
+ summary: { total: 0, verified: 0, failed: 0 },
57
+ };
58
+ }
59
+
60
+ // Verify each claim
61
+ const claimResults: ClaimVerificationResult[] = [];
62
+
63
+ for (const record of claimRecords) {
64
+ const result = await verifySingleClaim(did, record.uri, record.rkey, record.value, options);
65
+ claimResults.push(result);
66
+ }
67
+
68
+ return {
69
+ did,
70
+ claims: claimResults,
71
+ summary: {
72
+ total: claimResults.length,
73
+ verified: claimResults.filter((c) => c.verified).length,
74
+ failed: claimResults.filter((c) => !c.verified).length,
75
+ },
76
+ };
77
+ }
78
+
79
+ /**
80
+ * Verify a single claim's signature.
81
+ */
82
+ async function verifySingleClaim(did: string, uri: string, rkey: string, claim: ClaimRecord, options?: VerifyOptions): Promise<ClaimVerificationResult> {
83
+ const steps: VerificationStep[] = [];
84
+
85
+ try {
86
+ // Step 1: Validate claim structure
87
+ if (!claim.sig?.src || !claim.sig?.attestation || !claim.sig?.signedAt) {
88
+ steps.push({
89
+ step: "validate_claim",
90
+ success: false,
91
+ error: "Missing signature fields",
92
+ });
93
+ return buildResult(uri, rkey, claim, false, steps, "Missing signature fields");
94
+ }
95
+ steps.push({ step: "validate_claim", success: true, detail: "Claim structure valid" });
96
+
97
+ // Step 2: Fetch the signing key
98
+ let keyRecord: KeyRecord;
99
+ try {
100
+ keyRecord = await getRecordByUri<KeyRecord>(claim.sig.src, options);
101
+ steps.push({ step: "fetch_key", success: true, detail: `Fetched key from ${claim.sig.src}` });
102
+ } catch (err) {
103
+ const error = `Failed to fetch signing key: ${err instanceof Error ? err.message : String(err)}`;
104
+ steps.push({ step: "fetch_key", success: false, error });
105
+ return buildResult(uri, rkey, claim, false, steps, error);
106
+ }
107
+
108
+ // Step 3: Parse the public JWK
109
+ let publicJwk: ES256PublicJwk;
110
+ try {
111
+ publicJwk = JSON.parse(keyRecord.publicJwk) as ES256PublicJwk;
112
+ if (publicJwk.kty !== "EC" || publicJwk.crv !== "P-256") {
113
+ throw new Error("Invalid key type");
114
+ }
115
+ steps.push({ step: "parse_key", success: true, detail: "Parsed ES256 public key" });
116
+ } catch (err) {
117
+ const error = `Invalid public key format: ${err instanceof Error ? err.message : String(err)}`;
118
+ steps.push({ step: "parse_key", success: false, error });
119
+ return buildResult(uri, rkey, claim, false, steps, error);
120
+ }
121
+
122
+ // Step 4: Reconstruct the signed claim data
123
+ const signedData: SignedClaimData = {
124
+ did,
125
+ subject: claim.identity.subject,
126
+ type: claim.type,
127
+ verifiedAt: claim.sig.signedAt,
128
+ };
129
+ steps.push({
130
+ step: "reconstruct_data",
131
+ success: true,
132
+ detail: `Reconstructed signed data for ${claim.type}:${claim.identity.subject}`,
133
+ });
134
+
135
+ // Step 5: Verify the signature
136
+ const isValid = await verifyES256Signature(signedData, claim.sig.attestation, publicJwk);
137
+
138
+ if (isValid) {
139
+ steps.push({ step: "verify_signature", success: true, detail: "Signature verified" });
140
+ return buildResult(uri, rkey, claim, true, steps);
141
+ } else {
142
+ steps.push({ step: "verify_signature", success: false, error: "Signature verification failed" });
143
+ return buildResult(uri, rkey, claim, false, steps, "Signature verification failed");
144
+ }
145
+ } catch (err) {
146
+ const error = `Unexpected error: ${err instanceof Error ? err.message : String(err)}`;
147
+ steps.push({ step: "unknown", success: false, error });
148
+ return buildResult(uri, rkey, claim, false, steps, error);
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Build a claim verification result.
154
+ */
155
+ function buildResult(uri: string, rkey: string, claim: ClaimRecord, verified: boolean, steps: VerificationStep[], error?: string): ClaimVerificationResult {
156
+ return {
157
+ uri,
158
+ rkey,
159
+ type: claim.type,
160
+ claimUri: claim.claimUri,
161
+ verified,
162
+ steps,
163
+ error,
164
+ identity: claim.identity,
165
+ claim,
166
+ };
167
+ }