@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/README.md +105 -0
- package/dist/atproto.d.ts +22 -0
- package/dist/atproto.d.ts.map +1 -0
- package/dist/atproto.js +134 -0
- package/dist/atproto.js.map +1 -0
- package/dist/constants.d.ts +6 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +6 -0
- package/dist/constants.js.map +1 -0
- package/dist/crypto/base64url.d.ts +9 -0
- package/dist/crypto/base64url.d.ts.map +1 -0
- package/dist/crypto/base64url.js +29 -0
- package/dist/crypto/base64url.js.map +1 -0
- package/dist/crypto/canonicalize.d.ts +12 -0
- package/dist/crypto/canonicalize.d.ts.map +1 -0
- package/dist/crypto/canonicalize.js +67 -0
- package/dist/crypto/canonicalize.js.map +1 -0
- package/dist/crypto/signature.d.ts +11 -0
- package/dist/crypto/signature.d.ts.map +1 -0
- package/dist/crypto/signature.js +31 -0
- package/dist/crypto/signature.js.map +1 -0
- package/dist/types.d.ts +125 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/verify.d.ts +19 -0
- package/dist/verify.d.ts.map +1 -0
- package/dist/verify.js +143 -0
- package/dist/verify.js.map +1 -0
- package/package.json +36 -0
- package/src/atproto.ts +177 -0
- package/src/constants.ts +5 -0
- package/src/crypto/base64url.ts +29 -0
- package/src/crypto/canonicalize.ts +74 -0
- package/src/crypto/signature.ts +36 -0
- package/src/types.ts +133 -0
- package/src/verify.ts +167 -0
package/README.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# @keytrace/claims
|
|
2
|
+
|
|
3
|
+
Get Keytrace identity claims, and also verify their signatures. Works in both browser and Node.js.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @keytrace/claims
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { getClaimsForHandle, getClaimsForDid } from '@keytrace/claims';
|
|
15
|
+
|
|
16
|
+
// Verify all claims for a handle
|
|
17
|
+
const result = await getClaimsForHandle('alice.bsky.social');
|
|
18
|
+
|
|
19
|
+
console.log(`${result.summary.verified}/${result.summary.total} claims verified`);
|
|
20
|
+
|
|
21
|
+
for (const claim of result.claims) {
|
|
22
|
+
if (claim.verified) {
|
|
23
|
+
console.log(`✓ ${claim.type}: ${claim.identity.subject}`);
|
|
24
|
+
} else {
|
|
25
|
+
console.log(`✗ ${claim.type}: ${claim.error}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Or verify by DID directly
|
|
30
|
+
const result2 = await getClaimsForDid('did:plc:abc123');
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## API
|
|
34
|
+
|
|
35
|
+
### `getClaimsForHandle(handle, options?)`
|
|
36
|
+
|
|
37
|
+
Verify all keytrace claims for an ATProto handle.
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
const result = await getClaimsForHandle('alice.bsky.social');
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### `getClaimsForDid(did, options?)`
|
|
44
|
+
|
|
45
|
+
Verify all keytrace claims for a DID.
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
const result = await getClaimsForDid('did:plc:abc123');
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Options
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
interface VerifyOptions {
|
|
55
|
+
fetch?: typeof fetch; // Custom fetch function
|
|
56
|
+
timeout?: number; // Request timeout in ms (default: 10000)
|
|
57
|
+
plcDirectoryUrl?: string; // PLC directory URL (default: https://plc.directory)
|
|
58
|
+
publicApiUrl?: string; // Public API URL (default: https://public.api.bsky.app)
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Return Type
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
interface VerificationResult {
|
|
66
|
+
did: string;
|
|
67
|
+
handle?: string;
|
|
68
|
+
claims: ClaimVerificationResult[];
|
|
69
|
+
summary: {
|
|
70
|
+
total: number;
|
|
71
|
+
verified: number;
|
|
72
|
+
failed: number;
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
interface ClaimVerificationResult {
|
|
77
|
+
uri: string; // AT URI of the claim
|
|
78
|
+
rkey: string; // Record key
|
|
79
|
+
type: string; // Claim type (github, dns, etc.)
|
|
80
|
+
claimUri: string; // The claimed identity URI
|
|
81
|
+
verified: boolean; // Whether signature is valid
|
|
82
|
+
steps: VerificationStep[]; // Verification steps performed
|
|
83
|
+
error?: string; // Error message if failed
|
|
84
|
+
identity: ClaimIdentity; // Identity info from the claim
|
|
85
|
+
claim: ClaimRecord; // Full claim record
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## How It Works
|
|
90
|
+
|
|
91
|
+
1. Resolves the handle to a DID (if needed)
|
|
92
|
+
2. Fetches the user's PDS endpoint from their DID document
|
|
93
|
+
3. Lists all `dev.keytrace.claim` records from their repo
|
|
94
|
+
4. For each claim:
|
|
95
|
+
- Fetches the signing key from the `sig.src` AT URI
|
|
96
|
+
- Reconstructs the signed claim data
|
|
97
|
+
- Verifies the ES256 signature using Web Crypto API
|
|
98
|
+
|
|
99
|
+
## Platform Support
|
|
100
|
+
|
|
101
|
+
- Node.js 18+
|
|
102
|
+
- Modern browsers (Chrome, Firefox, Safari, Edge)
|
|
103
|
+
- Deno, Cloudflare Workers, and other runtimes with Web Crypto API
|
|
104
|
+
|
|
105
|
+
Zero runtime dependencies - uses standard `fetch` and `crypto.subtle` APIs.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ClaimRecord, VerifyOptions } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Resolve a handle to a DID using the public ATProto API.
|
|
4
|
+
*/
|
|
5
|
+
export declare function resolveHandle(handle: string, options?: VerifyOptions): Promise<string>;
|
|
6
|
+
/**
|
|
7
|
+
* Resolve the PDS endpoint from a DID document.
|
|
8
|
+
*/
|
|
9
|
+
export declare function resolvePds(did: string, options?: VerifyOptions): Promise<string>;
|
|
10
|
+
/**
|
|
11
|
+
* List all keytrace claim records from a user's repo.
|
|
12
|
+
*/
|
|
13
|
+
export declare function listClaimRecords(pdsUrl: string, did: string, options?: VerifyOptions): Promise<Array<{
|
|
14
|
+
uri: string;
|
|
15
|
+
rkey: string;
|
|
16
|
+
value: ClaimRecord;
|
|
17
|
+
}>>;
|
|
18
|
+
/**
|
|
19
|
+
* Fetch a single record by AT URI.
|
|
20
|
+
*/
|
|
21
|
+
export declare function getRecordByUri<T>(atUri: string, options?: VerifyOptions): Promise<T>;
|
|
22
|
+
//# sourceMappingURL=atproto.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"atproto.d.ts","sourceRoot":"","sources":["../src/atproto.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAa,aAAa,EAAE,MAAM,YAAY,CAAC;AA6CxE;;GAEG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAgB5F;AAED;;GAEG;AACH,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CA0BtF;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,KAAK,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,WAAW,CAAA;CAAE,CAAC,CAAC,CA0C9J;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,CAAC,CAAC,CAqB1F"}
|
package/dist/atproto.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { PUBLIC_API_URL, PLC_DIRECTORY_URL, COLLECTION_NSID, DEFAULT_TIMEOUT } from "./constants.js";
|
|
2
|
+
/**
|
|
3
|
+
* Fetch with timeout support
|
|
4
|
+
*/
|
|
5
|
+
async function fetchWithTimeout(url, options) {
|
|
6
|
+
const fetchFn = options?.fetch ?? globalThis.fetch;
|
|
7
|
+
const timeout = options?.timeout ?? DEFAULT_TIMEOUT;
|
|
8
|
+
const controller = new AbortController();
|
|
9
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
10
|
+
try {
|
|
11
|
+
const response = await fetchFn(url, { signal: controller.signal });
|
|
12
|
+
return response;
|
|
13
|
+
}
|
|
14
|
+
finally {
|
|
15
|
+
clearTimeout(timeoutId);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Resolve a handle to a DID using the public ATProto API.
|
|
20
|
+
*/
|
|
21
|
+
export async function resolveHandle(handle, options) {
|
|
22
|
+
// If it's already a DID, return it
|
|
23
|
+
if (handle.startsWith("did:")) {
|
|
24
|
+
return handle;
|
|
25
|
+
}
|
|
26
|
+
const baseUrl = options?.publicApiUrl ?? PUBLIC_API_URL;
|
|
27
|
+
const url = `${baseUrl}/xrpc/com.atproto.identity.resolveHandle?handle=${encodeURIComponent(handle)}`;
|
|
28
|
+
const response = await fetchWithTimeout(url, options);
|
|
29
|
+
if (!response.ok) {
|
|
30
|
+
throw new Error(`Failed to resolve handle: ${response.status} ${response.statusText}`);
|
|
31
|
+
}
|
|
32
|
+
const data = (await response.json());
|
|
33
|
+
return data.did;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Resolve the PDS endpoint from a DID document.
|
|
37
|
+
*/
|
|
38
|
+
export async function resolvePds(did, options) {
|
|
39
|
+
const plcUrl = options?.plcDirectoryUrl ?? PLC_DIRECTORY_URL;
|
|
40
|
+
try {
|
|
41
|
+
let url;
|
|
42
|
+
if (did.startsWith("did:plc:")) {
|
|
43
|
+
url = `${plcUrl}/${did}`;
|
|
44
|
+
}
|
|
45
|
+
else if (did.startsWith("did:web:")) {
|
|
46
|
+
const host = did.replace("did:web:", "").replaceAll(":", "/");
|
|
47
|
+
url = `https://${host}/.well-known/did.json`;
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
return PUBLIC_API_URL;
|
|
51
|
+
}
|
|
52
|
+
const response = await fetchWithTimeout(url, options);
|
|
53
|
+
if (!response.ok) {
|
|
54
|
+
return PUBLIC_API_URL;
|
|
55
|
+
}
|
|
56
|
+
const doc = (await response.json());
|
|
57
|
+
const pdsService = doc.service?.find((s) => s.id === "#atproto_pds" || s.type === "AtprotoPersonalDataServer");
|
|
58
|
+
return pdsService?.serviceEndpoint ?? PUBLIC_API_URL;
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return PUBLIC_API_URL;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* List all keytrace claim records from a user's repo.
|
|
66
|
+
*/
|
|
67
|
+
export async function listClaimRecords(pdsUrl, did, options) {
|
|
68
|
+
const claims = [];
|
|
69
|
+
try {
|
|
70
|
+
let cursor;
|
|
71
|
+
do {
|
|
72
|
+
const url = new URL(`${pdsUrl}/xrpc/com.atproto.repo.listRecords`);
|
|
73
|
+
url.searchParams.set("repo", did);
|
|
74
|
+
url.searchParams.set("collection", COLLECTION_NSID);
|
|
75
|
+
url.searchParams.set("limit", "100");
|
|
76
|
+
if (cursor)
|
|
77
|
+
url.searchParams.set("cursor", cursor);
|
|
78
|
+
const response = await fetchWithTimeout(url.toString(), options);
|
|
79
|
+
if (!response.ok) {
|
|
80
|
+
// No records or repo not found
|
|
81
|
+
if (response.status === 400 || response.status === 404) {
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
throw new Error(`Failed to list records: ${response.status}`);
|
|
85
|
+
}
|
|
86
|
+
const data = (await response.json());
|
|
87
|
+
for (const record of data.records) {
|
|
88
|
+
const rkey = parseAtUriRkey(record.uri);
|
|
89
|
+
claims.push({
|
|
90
|
+
uri: record.uri,
|
|
91
|
+
rkey,
|
|
92
|
+
value: record.value,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
cursor = data.cursor;
|
|
96
|
+
} while (cursor);
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
// Silently handle errors - return whatever we got
|
|
100
|
+
if (claims.length === 0) {
|
|
101
|
+
throw err;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return claims;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Fetch a single record by AT URI.
|
|
108
|
+
*/
|
|
109
|
+
export async function getRecordByUri(atUri, options) {
|
|
110
|
+
const match = atUri.match(/^at:\/\/([^/]+)\/([^/]+)\/(.+)$/);
|
|
111
|
+
if (!match) {
|
|
112
|
+
throw new Error(`Invalid AT URI: ${atUri}`);
|
|
113
|
+
}
|
|
114
|
+
const [, repo, collection, rkey] = match;
|
|
115
|
+
const pdsUrl = await resolvePds(repo, options);
|
|
116
|
+
const url = new URL(`${pdsUrl}/xrpc/com.atproto.repo.getRecord`);
|
|
117
|
+
url.searchParams.set("repo", repo);
|
|
118
|
+
url.searchParams.set("collection", collection);
|
|
119
|
+
url.searchParams.set("rkey", rkey);
|
|
120
|
+
const response = await fetchWithTimeout(url.toString(), options);
|
|
121
|
+
if (!response.ok) {
|
|
122
|
+
throw new Error(`Failed to fetch record: ${response.status}`);
|
|
123
|
+
}
|
|
124
|
+
const data = (await response.json());
|
|
125
|
+
return data.value;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Parse the rkey from an AT URI.
|
|
129
|
+
*/
|
|
130
|
+
function parseAtUriRkey(atUri) {
|
|
131
|
+
const match = atUri.match(/^at:\/\/[^/]+\/[^/]+\/(.+)$/);
|
|
132
|
+
return match?.[1] ?? "";
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=atproto.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"atproto.js","sourceRoot":"","sources":["../src/atproto.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const PUBLIC_API_URL = "https://public.api.bsky.app";
|
|
2
|
+
export declare const PLC_DIRECTORY_URL = "https://plc.directory";
|
|
3
|
+
export declare const COLLECTION_NSID = "dev.keytrace.claim";
|
|
4
|
+
export declare const KEY_COLLECTION_NSID = "dev.keytrace.key";
|
|
5
|
+
export declare const DEFAULT_TIMEOUT = 10000;
|
|
6
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc,gCAAgC,CAAC;AAC5D,eAAO,MAAM,iBAAiB,0BAA0B,CAAC;AACzD,eAAO,MAAM,eAAe,uBAAuB,CAAC;AACpD,eAAO,MAAM,mBAAmB,qBAAqB,CAAC;AACtD,eAAO,MAAM,eAAe,QAAQ,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export const PUBLIC_API_URL = "https://public.api.bsky.app";
|
|
2
|
+
export const PLC_DIRECTORY_URL = "https://plc.directory";
|
|
3
|
+
export const COLLECTION_NSID = "dev.keytrace.claim";
|
|
4
|
+
export const KEY_COLLECTION_NSID = "dev.keytrace.key";
|
|
5
|
+
export const DEFAULT_TIMEOUT = 10000;
|
|
6
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,cAAc,GAAG,6BAA6B,CAAC;AAC5D,MAAM,CAAC,MAAM,iBAAiB,GAAG,uBAAuB,CAAC;AACzD,MAAM,CAAC,MAAM,eAAe,GAAG,oBAAoB,CAAC;AACpD,MAAM,CAAC,MAAM,mBAAmB,GAAG,kBAAkB,CAAC;AACtD,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base64url decode a string to UTF-8 text.
|
|
3
|
+
*/
|
|
4
|
+
export declare function base64urlDecode(str: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* Base64url decode a string to raw bytes.
|
|
7
|
+
*/
|
|
8
|
+
export declare function base64urlDecodeToBytes(str: string): Uint8Array;
|
|
9
|
+
//# sourceMappingURL=base64url.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base64url.d.ts","sourceRoot":"","sources":["../../src/crypto/base64url.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAGnD;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAiB9D"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base64url decode a string to UTF-8 text.
|
|
3
|
+
*/
|
|
4
|
+
export function base64urlDecode(str) {
|
|
5
|
+
const bytes = base64urlDecodeToBytes(str);
|
|
6
|
+
return new TextDecoder().decode(bytes);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Base64url decode a string to raw bytes.
|
|
10
|
+
*/
|
|
11
|
+
export function base64urlDecodeToBytes(str) {
|
|
12
|
+
// Add padding if needed
|
|
13
|
+
let padded = str;
|
|
14
|
+
const remainder = str.length % 4;
|
|
15
|
+
if (remainder === 2)
|
|
16
|
+
padded += "==";
|
|
17
|
+
else if (remainder === 3)
|
|
18
|
+
padded += "=";
|
|
19
|
+
// Convert URL-safe characters back to standard base64
|
|
20
|
+
const base64 = padded.replace(/-/g, "+").replace(/_/g, "/");
|
|
21
|
+
// Decode - works in both browser and Node.js
|
|
22
|
+
const binary = atob(base64);
|
|
23
|
+
const bytes = new Uint8Array(binary.length);
|
|
24
|
+
for (let i = 0; i < binary.length; i++) {
|
|
25
|
+
bytes[i] = binary.charCodeAt(i);
|
|
26
|
+
}
|
|
27
|
+
return bytes;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=base64url.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base64url.js","sourceRoot":"","sources":["../../src/crypto/base64url.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,MAAM,KAAK,GAAG,sBAAsB,CAAC,GAAG,CAAC,CAAC;IAC1C,OAAO,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACzC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,sBAAsB,CAAC,GAAW;IAChD,wBAAwB;IACxB,IAAI,MAAM,GAAG,GAAG,CAAC;IACjB,MAAM,SAAS,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC;IACjC,IAAI,SAAS,KAAK,CAAC;QAAE,MAAM,IAAI,IAAI,CAAC;SAC/B,IAAI,SAAS,KAAK,CAAC;QAAE,MAAM,IAAI,GAAG,CAAC;IAExC,sDAAsD;IACtD,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAE5D,6CAA6C;IAC7C,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;IAC5B,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC5C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,KAAK,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IAClC,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonicalize a value according to RFC 8785 (JSON Canonicalization Scheme).
|
|
3
|
+
* https://datatracker.ietf.org/doc/html/rfc8785
|
|
4
|
+
*
|
|
5
|
+
* This ensures deterministic JSON output for cryptographic signing by:
|
|
6
|
+
* - Sorting object keys by UTF-16 code units
|
|
7
|
+
* - Serializing numbers per ES2020 Number.toString() (no -0, no NaN/Infinity)
|
|
8
|
+
* - No optional whitespace
|
|
9
|
+
* - Proper string escaping per RFC 8259
|
|
10
|
+
*/
|
|
11
|
+
export declare function canonicalize(data: unknown): string;
|
|
12
|
+
//# sourceMappingURL=canonicalize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canonicalize.d.ts","sourceRoot":"","sources":["../../src/crypto/canonicalize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAElD"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonicalize a value according to RFC 8785 (JSON Canonicalization Scheme).
|
|
3
|
+
* https://datatracker.ietf.org/doc/html/rfc8785
|
|
4
|
+
*
|
|
5
|
+
* This ensures deterministic JSON output for cryptographic signing by:
|
|
6
|
+
* - Sorting object keys by UTF-16 code units
|
|
7
|
+
* - Serializing numbers per ES2020 Number.toString() (no -0, no NaN/Infinity)
|
|
8
|
+
* - No optional whitespace
|
|
9
|
+
* - Proper string escaping per RFC 8259
|
|
10
|
+
*/
|
|
11
|
+
export function canonicalize(data) {
|
|
12
|
+
return serialize(data);
|
|
13
|
+
}
|
|
14
|
+
function serialize(value) {
|
|
15
|
+
if (value === null) {
|
|
16
|
+
return "null";
|
|
17
|
+
}
|
|
18
|
+
switch (typeof value) {
|
|
19
|
+
case "boolean":
|
|
20
|
+
return value ? "true" : "false";
|
|
21
|
+
case "number":
|
|
22
|
+
if (!Number.isFinite(value)) {
|
|
23
|
+
throw new Error("RFC 8785: Cannot serialize Infinity or NaN");
|
|
24
|
+
}
|
|
25
|
+
// RFC 8785 Section 3.2.2.3: -0 must be serialized as 0
|
|
26
|
+
if (Object.is(value, -0)) {
|
|
27
|
+
return "0";
|
|
28
|
+
}
|
|
29
|
+
// Use ES Number.toString() which produces RFC 8785 compliant output
|
|
30
|
+
return String(value);
|
|
31
|
+
case "string":
|
|
32
|
+
// JSON.stringify handles RFC 8259 string escaping
|
|
33
|
+
return JSON.stringify(value);
|
|
34
|
+
case "object":
|
|
35
|
+
if (Array.isArray(value)) {
|
|
36
|
+
return "[" + value.map(serialize).join(",") + "]";
|
|
37
|
+
}
|
|
38
|
+
return serializeObject(value);
|
|
39
|
+
default:
|
|
40
|
+
throw new Error(`RFC 8785: Cannot serialize value of type ${typeof value}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Serialize an object with keys sorted by UTF-16 code units per RFC 8785 Section 3.2.3.
|
|
45
|
+
*/
|
|
46
|
+
function serializeObject(obj) {
|
|
47
|
+
// RFC 8785: Sort keys by comparing UTF-16 code units
|
|
48
|
+
const keys = Object.keys(obj).sort((a, b) => {
|
|
49
|
+
const len = Math.min(a.length, b.length);
|
|
50
|
+
for (let i = 0; i < len; i++) {
|
|
51
|
+
const diff = a.charCodeAt(i) - b.charCodeAt(i);
|
|
52
|
+
if (diff !== 0)
|
|
53
|
+
return diff;
|
|
54
|
+
}
|
|
55
|
+
return a.length - b.length;
|
|
56
|
+
});
|
|
57
|
+
const pairs = [];
|
|
58
|
+
for (const key of keys) {
|
|
59
|
+
const val = obj[key];
|
|
60
|
+
// Skip undefined values (not valid in JSON)
|
|
61
|
+
if (val !== undefined) {
|
|
62
|
+
pairs.push(JSON.stringify(key) + ":" + serialize(val));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return "{" + pairs.join(",") + "}";
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=canonicalize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canonicalize.js","sourceRoot":"","sources":["../../src/crypto/canonicalize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,UAAU,YAAY,CAAC,IAAa;IACxC,OAAO,SAAS,CAAC,IAAI,CAAC,CAAC;AACzB,CAAC;AAED,SAAS,SAAS,CAAC,KAAc;IAC/B,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACnB,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,QAAQ,OAAO,KAAK,EAAE,CAAC;QACrB,KAAK,SAAS;YACZ,OAAO,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC;QAElC,KAAK,QAAQ;YACX,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC5B,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;YAChE,CAAC;YACD,uDAAuD;YACvD,IAAI,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACzB,OAAO,GAAG,CAAC;YACb,CAAC;YACD,oEAAoE;YACpE,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;QAEvB,KAAK,QAAQ;YACX,kDAAkD;YAClD,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QAE/B,KAAK,QAAQ;YACX,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;gBACzB,OAAO,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;YACpD,CAAC;YACD,OAAO,eAAe,CAAC,KAAgC,CAAC,CAAC;QAE3D;YACE,MAAM,IAAI,KAAK,CAAC,4CAA4C,OAAO,KAAK,EAAE,CAAC,CAAC;IAChF,CAAC;AACH,CAAC;AAED;;GAEG;AACH,SAAS,eAAe,CAAC,GAA4B;IACnD,qDAAqD;IACrD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QAC1C,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;QACzC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;YAC7B,MAAM,IAAI,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;YAC/C,IAAI,IAAI,KAAK,CAAC;gBAAE,OAAO,IAAI,CAAC;QAC9B,CAAC;QACD,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC;IAC7B,CAAC,CAAC,CAAC;IAEH,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;QACrB,4CAA4C;QAC5C,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YACtB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,GAAG,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;QACzD,CAAC;IACH,CAAC;IAED,OAAO,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;AACrC,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ES256PublicJwk, SignedClaimData } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Verify a JWS ES256 signature using Web Crypto API.
|
|
4
|
+
*
|
|
5
|
+
* @param claimData The claim data that was signed
|
|
6
|
+
* @param jws The JWS compact serialization (header.payload.signature)
|
|
7
|
+
* @param publicJwk The P-256 public key as JWK
|
|
8
|
+
* @returns true if signature is valid, false otherwise
|
|
9
|
+
*/
|
|
10
|
+
export declare function verifyES256Signature(claimData: SignedClaimData, jws: string, publicJwk: ES256PublicJwk): Promise<boolean>;
|
|
11
|
+
//# sourceMappingURL=signature.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signature.d.ts","sourceRoot":"","sources":["../../src/crypto/signature.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAInE;;;;;;;GAOG;AACH,wBAAsB,oBAAoB,CAAC,SAAS,EAAE,eAAe,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,CAuB/H"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { canonicalize } from "./canonicalize.js";
|
|
2
|
+
import { base64urlDecode, base64urlDecodeToBytes } from "./base64url.js";
|
|
3
|
+
/**
|
|
4
|
+
* Verify a JWS ES256 signature using Web Crypto API.
|
|
5
|
+
*
|
|
6
|
+
* @param claimData The claim data that was signed
|
|
7
|
+
* @param jws The JWS compact serialization (header.payload.signature)
|
|
8
|
+
* @param publicJwk The P-256 public key as JWK
|
|
9
|
+
* @returns true if signature is valid, false otherwise
|
|
10
|
+
*/
|
|
11
|
+
export async function verifyES256Signature(claimData, jws, publicJwk) {
|
|
12
|
+
const parts = jws.split(".");
|
|
13
|
+
if (parts.length !== 3)
|
|
14
|
+
return false;
|
|
15
|
+
const [headerB64, payloadB64, signatureB64] = parts;
|
|
16
|
+
// Verify payload matches expected canonical form
|
|
17
|
+
const expectedPayload = canonicalize(claimData);
|
|
18
|
+
const actualPayload = base64urlDecode(payloadB64);
|
|
19
|
+
if (actualPayload !== expectedPayload)
|
|
20
|
+
return false;
|
|
21
|
+
// Import JWK as CryptoKey
|
|
22
|
+
const key = await crypto.subtle.importKey("jwk", { ...publicJwk, alg: "ES256", use: "sig" }, { name: "ECDSA", namedCurve: "P-256" }, false, ["verify"]);
|
|
23
|
+
// Web Crypto expects raw signature bytes (R||S format) - which is what JWS ES256 uses
|
|
24
|
+
const signatureBytes = base64urlDecodeToBytes(signatureB64);
|
|
25
|
+
const signingInput = new TextEncoder().encode(`${headerB64}.${payloadB64}`);
|
|
26
|
+
// Create a fresh ArrayBuffer to satisfy TypeScript's strict BufferSource type
|
|
27
|
+
const signatureBuffer = new ArrayBuffer(signatureBytes.length);
|
|
28
|
+
new Uint8Array(signatureBuffer).set(signatureBytes);
|
|
29
|
+
return crypto.subtle.verify({ name: "ECDSA", hash: "SHA-256" }, key, signatureBuffer, signingInput);
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=signature.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signature.js","sourceRoot":"","sources":["../../src/crypto/signature.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAEzE;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,SAA0B,EAAE,GAAW,EAAE,SAAyB;IAC3G,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC7B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAErC,MAAM,CAAC,SAAS,EAAE,UAAU,EAAE,YAAY,CAAC,GAAG,KAAK,CAAC;IAEpD,iDAAiD;IACjD,MAAM,eAAe,GAAG,YAAY,CAAC,SAA+C,CAAC,CAAC;IACtF,MAAM,aAAa,GAAG,eAAe,CAAC,UAAU,CAAC,CAAC;IAClD,IAAI,aAAa,KAAK,eAAe;QAAE,OAAO,KAAK,CAAC;IAEpD,0BAA0B;IAC1B,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,EAAE,GAAG,SAAS,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;IAExJ,sFAAsF;IACtF,MAAM,cAAc,GAAG,sBAAsB,CAAC,YAAY,CAAC,CAAC;IAC5D,MAAM,YAAY,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,GAAG,SAAS,IAAI,UAAU,EAAE,CAAC,CAAC;IAE5E,8EAA8E;IAC9E,MAAM,eAAe,GAAG,IAAI,WAAW,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;IAC/D,IAAI,UAAU,CAAC,eAAe,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAEpD,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,GAAG,EAAE,eAAe,EAAE,YAAY,CAAC,CAAC;AACtG,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity metadata from a claim record
|
|
3
|
+
*/
|
|
4
|
+
export interface ClaimIdentity {
|
|
5
|
+
subject: string;
|
|
6
|
+
avatarUrl?: string;
|
|
7
|
+
profileUrl?: string;
|
|
8
|
+
displayName?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Signature data from a claim record
|
|
12
|
+
*/
|
|
13
|
+
export interface ClaimSignature {
|
|
14
|
+
/** Key identifier (YYYY-MM-DD) */
|
|
15
|
+
kid: string;
|
|
16
|
+
/** AT URI to the signing key record */
|
|
17
|
+
src: string;
|
|
18
|
+
/** Timestamp when signed */
|
|
19
|
+
signedAt: string;
|
|
20
|
+
/** JWS compact serialization */
|
|
21
|
+
attestation: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Raw claim record from ATProto
|
|
25
|
+
*/
|
|
26
|
+
export interface ClaimRecord {
|
|
27
|
+
$type: "dev.keytrace.claim";
|
|
28
|
+
type: string;
|
|
29
|
+
claimUri: string;
|
|
30
|
+
identity: ClaimIdentity;
|
|
31
|
+
sig: ClaimSignature;
|
|
32
|
+
comment?: string;
|
|
33
|
+
createdAt: string;
|
|
34
|
+
prerelease?: boolean;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Public key record from keytrace service
|
|
38
|
+
*/
|
|
39
|
+
export interface KeyRecord {
|
|
40
|
+
$type: "dev.keytrace.key";
|
|
41
|
+
publicJwk: string;
|
|
42
|
+
validFrom: string;
|
|
43
|
+
validUntil: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Parsed JWK for P-256 public key
|
|
47
|
+
*/
|
|
48
|
+
export interface ES256PublicJwk {
|
|
49
|
+
kty: "EC";
|
|
50
|
+
crv: "P-256";
|
|
51
|
+
x: string;
|
|
52
|
+
y: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Claim data that is signed (canonical form)
|
|
56
|
+
*/
|
|
57
|
+
export interface SignedClaimData {
|
|
58
|
+
did: string;
|
|
59
|
+
subject: string;
|
|
60
|
+
type: string;
|
|
61
|
+
verifiedAt: string;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Verification step detail for debugging/auditing
|
|
65
|
+
*/
|
|
66
|
+
export interface VerificationStep {
|
|
67
|
+
step: string;
|
|
68
|
+
success: boolean;
|
|
69
|
+
detail?: string;
|
|
70
|
+
error?: string;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Result of verifying a single claim
|
|
74
|
+
*/
|
|
75
|
+
export interface ClaimVerificationResult {
|
|
76
|
+
/** AT URI of the claim record */
|
|
77
|
+
uri: string;
|
|
78
|
+
/** Record key */
|
|
79
|
+
rkey: string;
|
|
80
|
+
/** Claim type (github, dns, etc.) */
|
|
81
|
+
type: string;
|
|
82
|
+
/** The claim URI being verified */
|
|
83
|
+
claimUri: string;
|
|
84
|
+
/** Whether signature verification passed */
|
|
85
|
+
verified: boolean;
|
|
86
|
+
/** Verification steps performed */
|
|
87
|
+
steps: VerificationStep[];
|
|
88
|
+
/** Error message if verification failed */
|
|
89
|
+
error?: string;
|
|
90
|
+
/** Identity data (available regardless of verification status) */
|
|
91
|
+
identity: ClaimIdentity;
|
|
92
|
+
/** Full claim record */
|
|
93
|
+
claim: ClaimRecord;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Result of verifying all claims for a DID
|
|
97
|
+
*/
|
|
98
|
+
export interface VerificationResult {
|
|
99
|
+
/** The DID that was verified */
|
|
100
|
+
did: string;
|
|
101
|
+
/** Resolved handle (if available) */
|
|
102
|
+
handle?: string;
|
|
103
|
+
/** Array of claim verification results */
|
|
104
|
+
claims: ClaimVerificationResult[];
|
|
105
|
+
/** Summary statistics */
|
|
106
|
+
summary: {
|
|
107
|
+
total: number;
|
|
108
|
+
verified: number;
|
|
109
|
+
failed: number;
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Options for verification
|
|
114
|
+
*/
|
|
115
|
+
export interface VerifyOptions {
|
|
116
|
+
/** Custom fetch function (defaults to globalThis.fetch) */
|
|
117
|
+
fetch?: typeof fetch;
|
|
118
|
+
/** Request timeout in ms (default: 10000) */
|
|
119
|
+
timeout?: number;
|
|
120
|
+
/** PLC directory URL (default: https://plc.directory) */
|
|
121
|
+
plcDirectoryUrl?: string;
|
|
122
|
+
/** Public ATProto API URL for handle resolution (default: https://public.api.bsky.app) */
|
|
123
|
+
publicApiUrl?: string;
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/dist/verify.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { VerificationResult, VerifyOptions } from "./types.js";
|
|
2
|
+
export type { ClaimIdentity, ClaimRecord, ClaimSignature, ClaimVerificationResult, ES256PublicJwk, KeyRecord, SignedClaimData, VerificationResult, VerificationStep, VerifyOptions, } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Verify all keytrace claims for a handle.
|
|
5
|
+
*
|
|
6
|
+
* @param handle The ATProto handle (e.g., "alice.bsky.social") or DID
|
|
7
|
+
* @param options Optional configuration
|
|
8
|
+
* @returns Verification results for all claims
|
|
9
|
+
*/
|
|
10
|
+
export declare function getClaimsForHandle(handle: string, options?: VerifyOptions): Promise<VerificationResult>;
|
|
11
|
+
/**
|
|
12
|
+
* Verify all keytrace claims for a DID.
|
|
13
|
+
*
|
|
14
|
+
* @param did The ATProto DID (e.g., "did:plc:abc123")
|
|
15
|
+
* @param options Optional configuration
|
|
16
|
+
* @returns Verification results for all claims
|
|
17
|
+
*/
|
|
18
|
+
export declare function getClaimsForDid(did: string, options?: VerifyOptions): Promise<VerificationResult>;
|
|
19
|
+
//# sourceMappingURL=verify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAoF,kBAAkB,EAAoB,aAAa,EAAE,MAAM,YAAY,CAAC;AAKxK,YAAY,EACV,aAAa,EACb,WAAW,EACX,cAAc,EACd,uBAAuB,EACvB,cAAc,EACd,SAAS,EACT,eAAe,EACf,kBAAkB,EAClB,gBAAgB,EAChB,aAAa,GACd,MAAM,YAAY,CAAC;AAEpB;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAQ7G;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAkCvG"}
|