@dwk/vc 0.1.0-beta.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.
- package/LICENSE +15 -0
- package/README.md +143 -0
- package/dist/config.d.ts +97 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +62 -0
- package/dist/config.js.map +1 -0
- package/dist/credential.d.ts +70 -0
- package/dist/credential.d.ts.map +1 -0
- package/dist/credential.js +139 -0
- package/dist/credential.js.map +1 -0
- package/dist/data-integrity.d.ts +102 -0
- package/dist/data-integrity.d.ts.map +1 -0
- package/dist/data-integrity.js +253 -0
- package/dist/data-integrity.js.map +1 -0
- package/dist/datetime.d.ts +26 -0
- package/dist/datetime.d.ts.map +1 -0
- package/dist/datetime.js +54 -0
- package/dist/datetime.js.map +1 -0
- package/dist/did-web.d.ts +93 -0
- package/dist/did-web.d.ts.map +1 -0
- package/dist/did-web.js +206 -0
- package/dist/did-web.js.map +1 -0
- package/dist/handler.d.ts +37 -0
- package/dist/handler.d.ts.map +1 -0
- package/dist/handler.js +362 -0
- package/dist/handler.js.map +1 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +46 -0
- package/dist/index.js.map +1 -0
- package/dist/jcs.d.ts +31 -0
- package/dist/jcs.d.ts.map +1 -0
- package/dist/jcs.js +67 -0
- package/dist/jcs.js.map +1 -0
- package/dist/log.d.ts +34 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +32 -0
- package/dist/log.js.map +1 -0
- package/dist/multibase.d.ts +57 -0
- package/dist/multibase.d.ts.map +1 -0
- package/dist/multibase.js +165 -0
- package/dist/multibase.js.map +1 -0
- package/dist/status-list.d.ts +116 -0
- package/dist/status-list.d.ts.map +1 -0
- package/dist/status-list.js +241 -0
- package/dist/status-list.js.map +1 -0
- package/package.json +48 -0
- package/src/config.ts +158 -0
- package/src/credential.ts +188 -0
- package/src/data-integrity.ts +425 -0
- package/src/datetime.ts +57 -0
- package/src/did-web.ts +273 -0
- package/src/handler.ts +477 -0
- package/src/index.ts +133 -0
- package/src/jcs.ts +83 -0
- package/src/log.ts +35 -0
- package/src/multibase.ts +189 -0
- package/src/status-list.ts +356 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 David W. Keith
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# `@dwk/vc`
|
|
2
|
+
|
|
3
|
+
> `did:web` identity plus Verifiable Credential (VCDM 2.0) issuance,
|
|
4
|
+
> verification, and revocation. Endpoint package (+ lib).
|
|
5
|
+
|
|
6
|
+
Part of the [`@dwk` IndieWeb + Solid cohort](../../README.md). See the
|
|
7
|
+
[package specification](../../spec/packages/vc.md) for the full requirements.
|
|
8
|
+
|
|
9
|
+
Decentralized identity rooted at the user's **own** domain. `did:web` is the same
|
|
10
|
+
WebID / IndieAuth identity root expressed as a DID, and Verifiable Credential
|
|
11
|
+
proofs reuse the project's asymmetric, allow-listed crypto posture — making
|
|
12
|
+
decentralized identity a low-marginal-cost capability on top of the rest of the
|
|
13
|
+
cohort.
|
|
14
|
+
|
|
15
|
+
## Worker vs. static (the split)
|
|
16
|
+
|
|
17
|
+
- The **`did:web` DID document is a static file** (`/.well-known/did.json`).
|
|
18
|
+
[`buildDidDocument`](src/did-web.ts) produces it — a static host (Anglesite)
|
|
19
|
+
can serve it, and **no Worker is needed to resolve a DID**.
|
|
20
|
+
- The Worker covers the **dynamic** parts: signing credentials with the domain's
|
|
21
|
+
key (**issuance**), **verification**, and **status / revocation**, whose
|
|
22
|
+
bit-flips need a strongly-consistent store.
|
|
23
|
+
|
|
24
|
+
## Proofs: JCS Data Integrity, not RDF canonicalization
|
|
25
|
+
|
|
26
|
+
Proofs use the **JCS** Data Integrity cryptosuites — `eddsa-jcs-2022` (Ed25519)
|
|
27
|
+
and `ecdsa-jcs-2019` (ECDSA P-256/P-384) — which canonicalize with
|
|
28
|
+
[RFC 8785](https://www.rfc-editor.org/rfc/rfc8785) (JSON), **not** RDF Dataset
|
|
29
|
+
Canonicalization. That keeps the package free of a JSON-LD/RDF canonicalizer
|
|
30
|
+
(`jsonld.js`/Comunica), well within the Worker script-size budget, and makes
|
|
31
|
+
proof construction a pure, plain-data transform that unit-tests without a Workers
|
|
32
|
+
runtime. Signing/verification mirror `@dwk/dpop` and `@dwk/http-signatures`:
|
|
33
|
+
asymmetric only, an explicit cryptosuite allow-list, and keys validated against
|
|
34
|
+
the claimed suite.
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { createVc } from "@dwk/vc";
|
|
40
|
+
|
|
41
|
+
const vc = createVc({
|
|
42
|
+
baseUrl: "https://example.com",
|
|
43
|
+
// did defaults to did:web:example.com; verificationMethod to ${did}#key-0
|
|
44
|
+
status: { enabled: true }, // revocation via a Bitstring Status List
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Bindings (composition contract):
|
|
48
|
+
// VC_SIGNING_KEY secret — the issuer's PRIVATE signing key as JWK JSON
|
|
49
|
+
// VC_STATUS_DB D1 — authoritative status bits (required iff status.enabled)
|
|
50
|
+
|
|
51
|
+
// In your Worker's fetch handler, mount the dynamic endpoints:
|
|
52
|
+
// POST /credentials/issue { credential } → { verifiableCredential }
|
|
53
|
+
// POST /credentials/verify { verifiableCredential } → { verified, errors, warnings }
|
|
54
|
+
// POST /credentials/status { credential | statusListIndex } → { status: "ok" }
|
|
55
|
+
// GET /credentials/status-lists/revocation → signed BitstringStatusListCredential
|
|
56
|
+
return vc(request, env, ctx);
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The signing key's public half must be published in the DID document's
|
|
60
|
+
verification method. Generate the document statically:
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
import { buildDidDocument, encodeEd25519Multikey } from "@dwk/vc";
|
|
64
|
+
|
|
65
|
+
const didDocument = buildDidDocument({
|
|
66
|
+
did: "did:web:example.com",
|
|
67
|
+
verificationMethods: [
|
|
68
|
+
{ id: "#key-0", publicKeyMultibase: encodeEd25519Multikey(rawEd25519PublicKey) },
|
|
69
|
+
],
|
|
70
|
+
});
|
|
71
|
+
// → write to /.well-known/did.json
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Issuance / verification as a library
|
|
75
|
+
|
|
76
|
+
Credential construction and the proof pipeline take plain-data inputs and need no
|
|
77
|
+
runtime:
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import { buildCredential, importSigner, addProof, verifyProof } from "@dwk/vc";
|
|
81
|
+
|
|
82
|
+
const signer = await importSigner(privateJwk); // picks the cryptosuite by key type
|
|
83
|
+
const credential = buildCredential({
|
|
84
|
+
issuer: "did:web:example.com",
|
|
85
|
+
credentialSubject: { id: "did:web:alice.example", alumniOf: "Example U" },
|
|
86
|
+
type: "AlumniCredential",
|
|
87
|
+
});
|
|
88
|
+
const vc = await addProof(credential, signer, {
|
|
89
|
+
verificationMethod: "did:web:example.com#key-0",
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const result = await verifyProof(vc, {
|
|
93
|
+
resolveVerificationMethod: createDidWebResolver(), // did:web over fetch
|
|
94
|
+
});
|
|
95
|
+
// → { verified: boolean, errors: string[] }
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Endpoints
|
|
99
|
+
|
|
100
|
+
- `POST {issueEndpoint}` (default `/credentials/issue`) — signs a credential with
|
|
101
|
+
the domain's key. When `status.enabled`, allocates and attaches a
|
|
102
|
+
`BitstringStatusListEntry` unless the request opts out (`credentialStatus:
|
|
103
|
+
false`) or the credential already carries one. → `{ verifiableCredential }`.
|
|
104
|
+
- `POST {verifyEndpoint}` (default `/credentials/verify`) — structural (VCDM 2.0)
|
|
105
|
+
+ validity-period + Data Integrity proof + status checks. Resolves keys via the
|
|
106
|
+
configured resolver (default `did:web`). → `{ verified, errors, warnings }`.
|
|
107
|
+
- `POST {statusEndpoint}` (default `/credentials/status`) — flips a credential's
|
|
108
|
+
status bit in D1 (revoke by default). Accepts a full `credential` (reads its
|
|
109
|
+
status entry) or an explicit `statusListIndex`. `404` when status is disabled.
|
|
110
|
+
- `GET {statusListEndpoint}/<purpose>` (default `/credentials/status-lists/…`) —
|
|
111
|
+
serves the **signed** `BitstringStatusListCredential` for that purpose.
|
|
112
|
+
|
|
113
|
+
Issuance and status are gated by the optional `authorize(operation, request)`
|
|
114
|
+
hook; when omitted, the composing Worker's front door owns edge token validation
|
|
115
|
+
(see [`spec/architecture.md`](../../spec/architecture.md)). Unmatched routes get
|
|
116
|
+
`404`; wrong methods get `405`.
|
|
117
|
+
|
|
118
|
+
## Design
|
|
119
|
+
|
|
120
|
+
- **Confinement / composition contract:** signing keys and issuer identity arrive
|
|
121
|
+
via config and secret bindings, never the global environment. The package
|
|
122
|
+
**fails loudly** when a required binding is missing (`VC_SIGNING_KEY`,
|
|
123
|
+
`VC_STATUS_DB` when status is enabled).
|
|
124
|
+
- **Strong consistency:** authoritative status bits live in **D1**, never KV —
|
|
125
|
+
staleness in a revocation check is a security bug
|
|
126
|
+
([`spec/non-functional-requirements.md`](../../spec/non-functional-requirements.md)).
|
|
127
|
+
- **Pure core:** `canonicalize` (JCS), the multibase/Multikey codecs, `addProof`
|
|
128
|
+
/`verifyProof`, the `did:web` mapping, and the bitstring codec are plain-data
|
|
129
|
+
(Web Crypto / `CompressionStream` only) and unit-test in isolation; only the
|
|
130
|
+
endpoints and the status store touch the runtime.
|
|
131
|
+
|
|
132
|
+
## Observability
|
|
133
|
+
|
|
134
|
+
Issuance, verification, status changes, and rejections are emitted through the
|
|
135
|
+
injected `@dwk/log` `Logger`/`Metrics` seams (default no-op): `vc.issued`,
|
|
136
|
+
`vc.verified`, `vc.status.changed`, `vc.rejected`. Credential subjects, claims,
|
|
137
|
+
signing keys, and proof values are **never** logged.
|
|
138
|
+
|
|
139
|
+
## Conformance
|
|
140
|
+
|
|
141
|
+
W3C VCDM 2.0, Data Integrity, and `did:web`. Status is tracked in
|
|
142
|
+
[`conformance/status.json`](../../conformance/status.json) and gated for stable
|
|
143
|
+
releases (see [`spec/conformance-and-testing.md`](../../spec/conformance-and-testing.md)).
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration for {@link ./handler.createVc}: the issuer DID and verification
|
|
3
|
+
* method, the dynamic endpoint URLs, the (optional) status-list policy, and the
|
|
4
|
+
* delegated authorization and DID-resolution hooks. Per the composition contract
|
|
5
|
+
* the package never reads the global environment — every tunable arrives here and
|
|
6
|
+
* signing-key material arrives via a secret binding — so a handler can be
|
|
7
|
+
* instantiated multiple times and tested in isolation.
|
|
8
|
+
*/
|
|
9
|
+
import { type Logger, type Metrics } from "@dwk/log";
|
|
10
|
+
import type { VerificationMethod } from "./data-integrity";
|
|
11
|
+
import { type StatusPurpose } from "./status-list";
|
|
12
|
+
/** A mutating operation an {@link AuthorizeOperation} hook can gate. */
|
|
13
|
+
export type VcOperation = "issue" | "status";
|
|
14
|
+
/**
|
|
15
|
+
* Authorization hook for the mutating endpoints (issue, status). Return `true`
|
|
16
|
+
* to allow. When omitted, requests are allowed through — the composing Worker is
|
|
17
|
+
* the front door and owns edge token validation (see `spec/architecture.md`);
|
|
18
|
+
* supply this to enforce authorization inside the package as well.
|
|
19
|
+
*/
|
|
20
|
+
export type AuthorizeOperation = (operation: VcOperation, request: Request) => boolean | Promise<boolean>;
|
|
21
|
+
/** Resolve a verification-method id to its key document, or `undefined`. */
|
|
22
|
+
export type DidResolver = (id: string) => VerificationMethod | undefined | Promise<VerificationMethod | undefined>;
|
|
23
|
+
/** Status-list policy. */
|
|
24
|
+
export interface StatusConfig {
|
|
25
|
+
/** Enable the status endpoints and `credentialStatus` issuance. */
|
|
26
|
+
readonly enabled: boolean;
|
|
27
|
+
/** The status purpose lists track. Defaults to `"revocation"`. */
|
|
28
|
+
readonly statusPurpose?: StatusPurpose;
|
|
29
|
+
/** Bit length of each status list. Defaults to {@link DEFAULT_STATUS_LIST_LENGTH}. */
|
|
30
|
+
readonly listLength?: number;
|
|
31
|
+
}
|
|
32
|
+
/** Configuration passed to {@link ./handler.createVc}. */
|
|
33
|
+
export interface VcConfig {
|
|
34
|
+
/** The identity root / base URL (e.g. `https://example.com`). */
|
|
35
|
+
readonly baseUrl: string;
|
|
36
|
+
/** The issuer DID. Defaults to the `did:web` derived from `baseUrl`. */
|
|
37
|
+
readonly did?: string;
|
|
38
|
+
/**
|
|
39
|
+
* The verification method id used to sign credentials (e.g.
|
|
40
|
+
* `did:web:example.com#key-0`). Defaults to `${did}#key-0`.
|
|
41
|
+
*/
|
|
42
|
+
readonly verificationMethod?: string;
|
|
43
|
+
/** Absolute issuance endpoint. Defaults to `${baseUrl}/credentials/issue`. */
|
|
44
|
+
readonly issueEndpoint?: string;
|
|
45
|
+
/** Absolute verification endpoint. Defaults to `${baseUrl}/credentials/verify`. */
|
|
46
|
+
readonly verifyEndpoint?: string;
|
|
47
|
+
/** Absolute status-update endpoint. Defaults to `${baseUrl}/credentials/status`. */
|
|
48
|
+
readonly statusEndpoint?: string;
|
|
49
|
+
/**
|
|
50
|
+
* Absolute base URL under which signed status list credentials are served. A
|
|
51
|
+
* request to `${statusListEndpoint}/<listId>` returns that list. Defaults to
|
|
52
|
+
* `${baseUrl}/credentials/status-lists`.
|
|
53
|
+
*/
|
|
54
|
+
readonly statusListEndpoint?: string;
|
|
55
|
+
/** Status-list policy. Disabled by default. */
|
|
56
|
+
readonly status?: StatusConfig;
|
|
57
|
+
/** Authorization hook for issue/status (see {@link AuthorizeOperation}). */
|
|
58
|
+
readonly authorize?: AuthorizeOperation;
|
|
59
|
+
/**
|
|
60
|
+
* Verification-method resolver used during verification. Defaults to a
|
|
61
|
+
* `did:web` resolver over the global `fetch`.
|
|
62
|
+
*/
|
|
63
|
+
readonly resolveDid?: DidResolver;
|
|
64
|
+
/** Logger; defaults to a no-op. */
|
|
65
|
+
readonly logger?: Logger;
|
|
66
|
+
/** Metrics sink; defaults to a no-op. */
|
|
67
|
+
readonly metrics?: Metrics;
|
|
68
|
+
}
|
|
69
|
+
/** Fully resolved configuration with defaults applied and URLs parsed. */
|
|
70
|
+
export interface ResolvedVcConfig {
|
|
71
|
+
readonly did: string;
|
|
72
|
+
readonly verificationMethod: string;
|
|
73
|
+
readonly issueEndpoint: string;
|
|
74
|
+
readonly verifyEndpoint: string;
|
|
75
|
+
readonly statusEndpoint: string;
|
|
76
|
+
readonly statusListEndpoint: string;
|
|
77
|
+
readonly issuePath: string;
|
|
78
|
+
readonly verifyPath: string;
|
|
79
|
+
readonly statusPath: string;
|
|
80
|
+
readonly statusListPath: string;
|
|
81
|
+
readonly statusEnabled: boolean;
|
|
82
|
+
readonly statusPurpose: StatusPurpose;
|
|
83
|
+
readonly statusListLength: number;
|
|
84
|
+
readonly authorize: AuthorizeOperation;
|
|
85
|
+
readonly resolveDid?: DidResolver;
|
|
86
|
+
readonly logger: Logger;
|
|
87
|
+
readonly metrics: Metrics;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Resolve user config into a {@link ResolvedVcConfig}: derive the issuer DID and
|
|
91
|
+
* verification method from `baseUrl` when omitted, default each endpoint URL, and
|
|
92
|
+
* pre-compute pathnames for routing. Throws if `baseUrl` (or any explicitly
|
|
93
|
+
* supplied endpoint URL) is not a valid URL, or if status is enabled without a
|
|
94
|
+
* status-list endpoint resolvable from `baseUrl`.
|
|
95
|
+
*/
|
|
96
|
+
export declare function resolveConfig(config: VcConfig): ResolvedVcConfig;
|
|
97
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration for {@link ./handler.createVc}: the issuer DID and verification
|
|
3
|
+
* method, the dynamic endpoint URLs, the (optional) status-list policy, and the
|
|
4
|
+
* delegated authorization and DID-resolution hooks. Per the composition contract
|
|
5
|
+
* the package never reads the global environment — every tunable arrives here and
|
|
6
|
+
* signing-key material arrives via a secret binding — so a handler can be
|
|
7
|
+
* instantiated multiple times and tested in isolation.
|
|
8
|
+
*/
|
|
9
|
+
import { noopLogger, noopMetrics } from "@dwk/log";
|
|
10
|
+
import { urlToDidWeb } from "./did-web";
|
|
11
|
+
import { DEFAULT_STATUS_LIST_LENGTH } from "./status-list";
|
|
12
|
+
function pathOf(absoluteUrl, label) {
|
|
13
|
+
try {
|
|
14
|
+
return new URL(absoluteUrl).pathname;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
throw new Error(`@dwk/vc: ${label} is not a valid URL`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Resolve user config into a {@link ResolvedVcConfig}: derive the issuer DID and
|
|
22
|
+
* verification method from `baseUrl` when omitted, default each endpoint URL, and
|
|
23
|
+
* pre-compute pathnames for routing. Throws if `baseUrl` (or any explicitly
|
|
24
|
+
* supplied endpoint URL) is not a valid URL, or if status is enabled without a
|
|
25
|
+
* status-list endpoint resolvable from `baseUrl`.
|
|
26
|
+
*/
|
|
27
|
+
export function resolveConfig(config) {
|
|
28
|
+
let base;
|
|
29
|
+
try {
|
|
30
|
+
base = new URL(config.baseUrl);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
throw new Error("@dwk/vc: `baseUrl` is not a valid URL");
|
|
34
|
+
}
|
|
35
|
+
const origin = base.origin;
|
|
36
|
+
const did = config.did ?? urlToDidWeb(config.baseUrl);
|
|
37
|
+
const verificationMethod = config.verificationMethod ?? `${did}#key-0`;
|
|
38
|
+
const issueEndpoint = config.issueEndpoint ?? `${origin}/credentials/issue`;
|
|
39
|
+
const verifyEndpoint = config.verifyEndpoint ?? `${origin}/credentials/verify`;
|
|
40
|
+
const statusEndpoint = config.statusEndpoint ?? `${origin}/credentials/status`;
|
|
41
|
+
const statusListEndpoint = config.statusListEndpoint ?? `${origin}/credentials/status-lists`;
|
|
42
|
+
return {
|
|
43
|
+
did,
|
|
44
|
+
verificationMethod,
|
|
45
|
+
issueEndpoint,
|
|
46
|
+
verifyEndpoint,
|
|
47
|
+
statusEndpoint,
|
|
48
|
+
statusListEndpoint,
|
|
49
|
+
issuePath: pathOf(issueEndpoint, "issueEndpoint"),
|
|
50
|
+
verifyPath: pathOf(verifyEndpoint, "verifyEndpoint"),
|
|
51
|
+
statusPath: pathOf(statusEndpoint, "statusEndpoint"),
|
|
52
|
+
statusListPath: pathOf(statusListEndpoint, "statusListEndpoint"),
|
|
53
|
+
statusEnabled: config.status?.enabled ?? false,
|
|
54
|
+
statusPurpose: config.status?.statusPurpose ?? "revocation",
|
|
55
|
+
statusListLength: config.status?.listLength ?? DEFAULT_STATUS_LIST_LENGTH,
|
|
56
|
+
authorize: config.authorize ?? (() => true),
|
|
57
|
+
resolveDid: config.resolveDid,
|
|
58
|
+
logger: config.logger ?? noopLogger,
|
|
59
|
+
metrics: config.metrics ?? noopMetrics,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,UAAU,EAAE,WAAW,EAA6B,MAAM,UAAU,CAAC;AAG9E,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,0BAA0B,EAAsB,MAAM,eAAe,CAAC;AA0F/E,SAAS,MAAM,CAAC,WAAmB,EAAE,KAAa;IAChD,IAAI,CAAC;QACH,OAAO,IAAI,GAAG,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC;IACvC,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,YAAY,KAAK,qBAAqB,CAAC,CAAC;IAC1D,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,MAAgB;IAC5C,IAAI,IAAS,CAAC;IACd,IAAI,CAAC;QACH,IAAI,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACjC,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;IAC3D,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;IAE3B,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,IAAI,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACtD,MAAM,kBAAkB,GAAG,MAAM,CAAC,kBAAkB,IAAI,GAAG,GAAG,QAAQ,CAAC;IAEvE,MAAM,aAAa,GAAG,MAAM,CAAC,aAAa,IAAI,GAAG,MAAM,oBAAoB,CAAC;IAC5E,MAAM,cAAc,GAClB,MAAM,CAAC,cAAc,IAAI,GAAG,MAAM,qBAAqB,CAAC;IAC1D,MAAM,cAAc,GAClB,MAAM,CAAC,cAAc,IAAI,GAAG,MAAM,qBAAqB,CAAC;IAC1D,MAAM,kBAAkB,GACtB,MAAM,CAAC,kBAAkB,IAAI,GAAG,MAAM,2BAA2B,CAAC;IAEpE,OAAO;QACL,GAAG;QACH,kBAAkB;QAClB,aAAa;QACb,cAAc;QACd,cAAc;QACd,kBAAkB;QAClB,SAAS,EAAE,MAAM,CAAC,aAAa,EAAE,eAAe,CAAC;QACjD,UAAU,EAAE,MAAM,CAAC,cAAc,EAAE,gBAAgB,CAAC;QACpD,UAAU,EAAE,MAAM,CAAC,cAAc,EAAE,gBAAgB,CAAC;QACpD,cAAc,EAAE,MAAM,CAAC,kBAAkB,EAAE,oBAAoB,CAAC;QAChE,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,IAAI,KAAK;QAC9C,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,IAAI,YAAY;QAC3D,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,IAAI,0BAA0B;QACzE,SAAS,EAAE,MAAM,CAAC,SAAS,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC;QAC3C,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,UAAU;QACnC,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,WAAW;KACvC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verifiable Credential Data Model 2.0 shapes and structural validation.
|
|
3
|
+
*
|
|
4
|
+
* Plain-data helpers: assemble an unsigned credential, validate the required VCDM
|
|
5
|
+
* 2.0 members before signing or after verifying, and evaluate the validity
|
|
6
|
+
* window. No Web Crypto, no runtime — the proof lives in {@link ./data-integrity}.
|
|
7
|
+
*
|
|
8
|
+
* @see https://www.w3.org/TR/vc-data-model-2.0/
|
|
9
|
+
*/
|
|
10
|
+
import type { JcsValue } from "./jcs";
|
|
11
|
+
import type { JsonObject } from "./data-integrity";
|
|
12
|
+
/** The base VCDM 2.0 context, which MUST be the first `@context` entry. */
|
|
13
|
+
export declare const VC_CONTEXT_V2 = "https://www.w3.org/ns/credentials/v2";
|
|
14
|
+
/** The base credential type every verifiable credential carries. */
|
|
15
|
+
export declare const VERIFIABLE_CREDENTIAL_TYPE = "VerifiableCredential";
|
|
16
|
+
/** An issuer reference: a URL string or an object with an `id`. */
|
|
17
|
+
export type Issuer = string | (JsonObject & {
|
|
18
|
+
id: string;
|
|
19
|
+
});
|
|
20
|
+
/** A credential prior to having a proof attached. */
|
|
21
|
+
export interface UnsignedCredential extends JsonObject {
|
|
22
|
+
"@context": JcsValue;
|
|
23
|
+
type: JcsValue;
|
|
24
|
+
issuer: Issuer;
|
|
25
|
+
credentialSubject: JcsValue;
|
|
26
|
+
}
|
|
27
|
+
/** Inputs to {@link buildCredential}. */
|
|
28
|
+
export interface BuildCredentialOptions {
|
|
29
|
+
/** Extra credential types beyond `VerifiableCredential`. */
|
|
30
|
+
readonly type?: string | readonly string[];
|
|
31
|
+
/** Extra `@context` entries beyond the base VCDM 2.0 context. */
|
|
32
|
+
readonly context?: string | readonly string[];
|
|
33
|
+
/** The credential id (a URL), if any. */
|
|
34
|
+
readonly id?: string;
|
|
35
|
+
readonly issuer: Issuer;
|
|
36
|
+
readonly credentialSubject: JcsValue;
|
|
37
|
+
/** `validFrom` (XSD dateTime). Defaults to now when omitted. */
|
|
38
|
+
readonly validFrom?: Date | string;
|
|
39
|
+
/** `validUntil` (XSD dateTime). */
|
|
40
|
+
readonly validUntil?: Date | string;
|
|
41
|
+
/** A `credentialStatus` entry (e.g. a Bitstring Status List reference). */
|
|
42
|
+
readonly credentialStatus?: JsonObject | readonly JsonObject[];
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Assemble an unsigned VCDM 2.0 credential, guaranteeing the base context comes
|
|
46
|
+
* first and the base `VerifiableCredential` type is present. The result is ready
|
|
47
|
+
* for {@link ./data-integrity.addProof}.
|
|
48
|
+
*/
|
|
49
|
+
export declare function buildCredential(options: BuildCredentialOptions): UnsignedCredential;
|
|
50
|
+
/**
|
|
51
|
+
* Validate the structural VCDM 2.0 requirements of a credential, returning a
|
|
52
|
+
* list of problems (empty when valid). Checks the base context ordering, the
|
|
53
|
+
* `VerifiableCredential` type, and the presence/typing of `issuer` and
|
|
54
|
+
* `credentialSubject`. Does not check the proof — that is
|
|
55
|
+
* {@link ./data-integrity.verifyProof}.
|
|
56
|
+
*/
|
|
57
|
+
export declare function validateCredential(credential: JsonObject): string[];
|
|
58
|
+
/** The issuer id of a credential, whether `issuer` is a string or an object. */
|
|
59
|
+
export declare function issuerId(credential: JsonObject): string | undefined;
|
|
60
|
+
/**
|
|
61
|
+
* Evaluate a credential's validity window against `now` (epoch ms). Returns a
|
|
62
|
+
* reason when the credential is not yet valid or has expired, else `null`.
|
|
63
|
+
*
|
|
64
|
+
* A present-but-malformed bound fails **closed**: an unparseable `validFrom` is
|
|
65
|
+
* treated as not-yet-valid and an unparseable `validUntil` as expired, rather
|
|
66
|
+
* than silently dropping the bound (which would let a credential with a garbled
|
|
67
|
+
* expiry verify as if it never expires).
|
|
68
|
+
*/
|
|
69
|
+
export declare function checkValidityPeriod(credential: JsonObject, now?: number): "not_yet_valid" | "expired" | null;
|
|
70
|
+
//# sourceMappingURL=credential.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credential.d.ts","sourceRoot":"","sources":["../src/credential.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACtC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,2EAA2E;AAC3E,eAAO,MAAM,aAAa,yCAAyC,CAAC;AAEpE,oEAAoE;AACpE,eAAO,MAAM,0BAA0B,yBAAyB,CAAC;AAEjE,mEAAmE;AACnE,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,UAAU,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE5D,qDAAqD;AACrD,MAAM,WAAW,kBAAmB,SAAQ,UAAU;IACpD,UAAU,EAAE,QAAQ,CAAC;IACrB,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,iBAAiB,EAAE,QAAQ,CAAC;CAC7B;AAED,yCAAyC;AACzC,MAAM,WAAW,sBAAsB;IACrC,4DAA4D;IAC5D,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC;IAC3C,iEAAiE;IACjE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC;IAC9C,yCAAyC;IACzC,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,iBAAiB,EAAE,QAAQ,CAAC;IACrC,gEAAgE;IAChE,QAAQ,CAAC,SAAS,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC;IACnC,mCAAmC;IACnC,QAAQ,CAAC,UAAU,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC;IACpC,2EAA2E;IAC3E,QAAQ,CAAC,gBAAgB,CAAC,EAAE,UAAU,GAAG,SAAS,UAAU,EAAE,CAAC;CAChE;AAeD;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,sBAAsB,GAC9B,kBAAkB,CAkBpB;AAYD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,EAAE,CAqCnE;AAED,gFAAgF;AAChF,wBAAgB,QAAQ,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,GAAG,SAAS,CAQnE;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,UAAU,EACtB,GAAG,GAAE,MAAmB,GACvB,eAAe,GAAG,SAAS,GAAG,IAAI,CAmBpC"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verifiable Credential Data Model 2.0 shapes and structural validation.
|
|
3
|
+
*
|
|
4
|
+
* Plain-data helpers: assemble an unsigned credential, validate the required VCDM
|
|
5
|
+
* 2.0 members before signing or after verifying, and evaluate the validity
|
|
6
|
+
* window. No Web Crypto, no runtime — the proof lives in {@link ./data-integrity}.
|
|
7
|
+
*
|
|
8
|
+
* @see https://www.w3.org/TR/vc-data-model-2.0/
|
|
9
|
+
*/
|
|
10
|
+
import { isValidXsdDateTimeStamp, toXsdDateTime } from "./datetime";
|
|
11
|
+
/** The base VCDM 2.0 context, which MUST be the first `@context` entry. */
|
|
12
|
+
export const VC_CONTEXT_V2 = "https://www.w3.org/ns/credentials/v2";
|
|
13
|
+
/** The base credential type every verifiable credential carries. */
|
|
14
|
+
export const VERIFIABLE_CREDENTIAL_TYPE = "VerifiableCredential";
|
|
15
|
+
function dedupePrepend(base, extra) {
|
|
16
|
+
const out = [base];
|
|
17
|
+
if (extra === undefined)
|
|
18
|
+
return out;
|
|
19
|
+
const list = typeof extra === "string" ? [extra] : extra;
|
|
20
|
+
for (const item of list) {
|
|
21
|
+
if (!out.includes(item))
|
|
22
|
+
out.push(item);
|
|
23
|
+
}
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Assemble an unsigned VCDM 2.0 credential, guaranteeing the base context comes
|
|
28
|
+
* first and the base `VerifiableCredential` type is present. The result is ready
|
|
29
|
+
* for {@link ./data-integrity.addProof}.
|
|
30
|
+
*/
|
|
31
|
+
export function buildCredential(options) {
|
|
32
|
+
const credential = {
|
|
33
|
+
"@context": dedupePrepend(VC_CONTEXT_V2, options.context),
|
|
34
|
+
type: dedupePrepend(VERIFIABLE_CREDENTIAL_TYPE, options.type),
|
|
35
|
+
issuer: options.issuer,
|
|
36
|
+
credentialSubject: options.credentialSubject,
|
|
37
|
+
};
|
|
38
|
+
if (options.id !== undefined)
|
|
39
|
+
credential.id = options.id;
|
|
40
|
+
credential.validFrom = toXsdDateTime(options.validFrom ?? new Date());
|
|
41
|
+
if (options.validUntil !== undefined) {
|
|
42
|
+
credential.validUntil = toXsdDateTime(options.validUntil);
|
|
43
|
+
}
|
|
44
|
+
if (options.credentialStatus !== undefined) {
|
|
45
|
+
credential.credentialStatus = Array.isArray(options.credentialStatus)
|
|
46
|
+
? [...options.credentialStatus]
|
|
47
|
+
: options.credentialStatus;
|
|
48
|
+
}
|
|
49
|
+
return credential;
|
|
50
|
+
}
|
|
51
|
+
function hasType(type, expected) {
|
|
52
|
+
if (type === expected)
|
|
53
|
+
return true;
|
|
54
|
+
return Array.isArray(type) && type.includes(expected);
|
|
55
|
+
}
|
|
56
|
+
function firstContext(context) {
|
|
57
|
+
if (Array.isArray(context))
|
|
58
|
+
return context[0];
|
|
59
|
+
return context;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Validate the structural VCDM 2.0 requirements of a credential, returning a
|
|
63
|
+
* list of problems (empty when valid). Checks the base context ordering, the
|
|
64
|
+
* `VerifiableCredential` type, and the presence/typing of `issuer` and
|
|
65
|
+
* `credentialSubject`. Does not check the proof — that is
|
|
66
|
+
* {@link ./data-integrity.verifyProof}.
|
|
67
|
+
*/
|
|
68
|
+
export function validateCredential(credential) {
|
|
69
|
+
const errors = [];
|
|
70
|
+
if (firstContext(credential["@context"]) !== VC_CONTEXT_V2) {
|
|
71
|
+
errors.push(`@context must begin with "${VC_CONTEXT_V2}"`);
|
|
72
|
+
}
|
|
73
|
+
if (!hasType(credential.type, VERIFIABLE_CREDENTIAL_TYPE)) {
|
|
74
|
+
errors.push(`type must include "${VERIFIABLE_CREDENTIAL_TYPE}"`);
|
|
75
|
+
}
|
|
76
|
+
const issuer = credential.issuer;
|
|
77
|
+
const issuerOk = typeof issuer === "string"
|
|
78
|
+
? issuer.length > 0
|
|
79
|
+
: issuer !== null &&
|
|
80
|
+
typeof issuer === "object" &&
|
|
81
|
+
!Array.isArray(issuer) &&
|
|
82
|
+
typeof issuer.id === "string";
|
|
83
|
+
if (!issuerOk) {
|
|
84
|
+
errors.push("issuer must be a URL string or an object with a string id");
|
|
85
|
+
}
|
|
86
|
+
const subject = credential.credentialSubject;
|
|
87
|
+
const subjectOk = subject !== null &&
|
|
88
|
+
subject !== undefined &&
|
|
89
|
+
typeof subject === "object" &&
|
|
90
|
+
(!Array.isArray(subject) || subject.length > 0);
|
|
91
|
+
if (!subjectOk) {
|
|
92
|
+
errors.push("credentialSubject must be an object or a non-empty array");
|
|
93
|
+
}
|
|
94
|
+
if (credential.id !== undefined && typeof credential.id !== "string") {
|
|
95
|
+
errors.push("id, when present, must be a string");
|
|
96
|
+
}
|
|
97
|
+
return errors;
|
|
98
|
+
}
|
|
99
|
+
/** The issuer id of a credential, whether `issuer` is a string or an object. */
|
|
100
|
+
export function issuerId(credential) {
|
|
101
|
+
const issuer = credential.issuer;
|
|
102
|
+
if (typeof issuer === "string")
|
|
103
|
+
return issuer;
|
|
104
|
+
if (issuer !== null && typeof issuer === "object" && !Array.isArray(issuer)) {
|
|
105
|
+
const id = issuer.id;
|
|
106
|
+
return typeof id === "string" ? id : undefined;
|
|
107
|
+
}
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Evaluate a credential's validity window against `now` (epoch ms). Returns a
|
|
112
|
+
* reason when the credential is not yet valid or has expired, else `null`.
|
|
113
|
+
*
|
|
114
|
+
* A present-but-malformed bound fails **closed**: an unparseable `validFrom` is
|
|
115
|
+
* treated as not-yet-valid and an unparseable `validUntil` as expired, rather
|
|
116
|
+
* than silently dropping the bound (which would let a credential with a garbled
|
|
117
|
+
* expiry verify as if it never expires).
|
|
118
|
+
*/
|
|
119
|
+
export function checkValidityPeriod(credential, now = Date.now()) {
|
|
120
|
+
const validFrom = credential.validFrom;
|
|
121
|
+
if (validFrom !== undefined) {
|
|
122
|
+
if (typeof validFrom !== "string" || !isValidXsdDateTimeStamp(validFrom)) {
|
|
123
|
+
return "not_yet_valid";
|
|
124
|
+
}
|
|
125
|
+
if (now < Date.parse(validFrom))
|
|
126
|
+
return "not_yet_valid";
|
|
127
|
+
}
|
|
128
|
+
const validUntil = credential.validUntil;
|
|
129
|
+
if (validUntil !== undefined) {
|
|
130
|
+
if (typeof validUntil !== "string" ||
|
|
131
|
+
!isValidXsdDateTimeStamp(validUntil)) {
|
|
132
|
+
return "expired";
|
|
133
|
+
}
|
|
134
|
+
if (now > Date.parse(validUntil))
|
|
135
|
+
return "expired";
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=credential.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credential.js","sourceRoot":"","sources":["../src/credential.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,uBAAuB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAIpE,2EAA2E;AAC3E,MAAM,CAAC,MAAM,aAAa,GAAG,sCAAsC,CAAC;AAEpE,oEAAoE;AACpE,MAAM,CAAC,MAAM,0BAA0B,GAAG,sBAAsB,CAAC;AA+BjE,SAAS,aAAa,CACpB,IAAY,EACZ,KAA6C;IAE7C,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC;IACnB,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,GAAG,CAAC;IACpC,MAAM,IAAI,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IACzD,KAAK,MAAM,IAAI,IAAI,IAAI,EAAE,CAAC;QACxB,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1C,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAC7B,OAA+B;IAE/B,MAAM,UAAU,GAAuB;QACrC,UAAU,EAAE,aAAa,CAAC,aAAa,EAAE,OAAO,CAAC,OAAO,CAAC;QACzD,IAAI,EAAE,aAAa,CAAC,0BAA0B,EAAE,OAAO,CAAC,IAAI,CAAC;QAC7D,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,iBAAiB,EAAE,OAAO,CAAC,iBAAiB;KAC7C,CAAC;IACF,IAAI,OAAO,CAAC,EAAE,KAAK,SAAS;QAAE,UAAU,CAAC,EAAE,GAAG,OAAO,CAAC,EAAE,CAAC;IACzD,UAAU,CAAC,SAAS,GAAG,aAAa,CAAC,OAAO,CAAC,SAAS,IAAI,IAAI,IAAI,EAAE,CAAC,CAAC;IACtE,IAAI,OAAO,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;QACrC,UAAU,CAAC,UAAU,GAAG,aAAa,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAC5D,CAAC;IACD,IAAI,OAAO,CAAC,gBAAgB,KAAK,SAAS,EAAE,CAAC;QAC3C,UAAU,CAAC,gBAAgB,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,gBAAgB,CAAC;YACnE,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,gBAAgB,CAAC;YAC/B,CAAC,CAAE,OAAO,CAAC,gBAA+B,CAAC;IAC/C,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,SAAS,OAAO,CAAC,IAA0B,EAAE,QAAgB;IAC3D,IAAI,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACnC,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AACxD,CAAC;AAED,SAAS,YAAY,CAAC,OAA6B;IACjD,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;QAAE,OAAO,OAAO,CAAC,CAAC,CAAC,CAAC;IAC9C,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAAC,UAAsB;IACvD,MAAM,MAAM,GAAa,EAAE,CAAC;IAE5B,IAAI,YAAY,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,KAAK,aAAa,EAAE,CAAC;QAC3D,MAAM,CAAC,IAAI,CAAC,6BAA6B,aAAa,GAAG,CAAC,CAAC;IAC7D,CAAC;IACD,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,EAAE,0BAA0B,CAAC,EAAE,CAAC;QAC1D,MAAM,CAAC,IAAI,CAAC,sBAAsB,0BAA0B,GAAG,CAAC,CAAC;IACnE,CAAC;IAED,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC;IACjC,MAAM,QAAQ,GACZ,OAAO,MAAM,KAAK,QAAQ;QACxB,CAAC,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC;QACnB,CAAC,CAAC,MAAM,KAAK,IAAI;YACf,OAAO,MAAM,KAAK,QAAQ;YAC1B,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;YACtB,OAAQ,MAAqB,CAAC,EAAE,KAAK,QAAQ,CAAC;IACpD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,CAAC,IAAI,CAAC,2DAA2D,CAAC,CAAC;IAC3E,CAAC;IAED,MAAM,OAAO,GAAG,UAAU,CAAC,iBAAiB,CAAC;IAC7C,MAAM,SAAS,GACb,OAAO,KAAK,IAAI;QAChB,OAAO,KAAK,SAAS;QACrB,OAAO,OAAO,KAAK,QAAQ;QAC3B,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAClD,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,MAAM,CAAC,IAAI,CAAC,0DAA0D,CAAC,CAAC;IAC1E,CAAC;IAED,IAAI,UAAU,CAAC,EAAE,KAAK,SAAS,IAAI,OAAO,UAAU,CAAC,EAAE,KAAK,QAAQ,EAAE,CAAC;QACrE,MAAM,CAAC,IAAI,CAAC,oCAAoC,CAAC,CAAC;IACpD,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,QAAQ,CAAC,UAAsB;IAC7C,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC;IACjC,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC;IAC9C,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC5E,MAAM,EAAE,GAAI,MAAqB,CAAC,EAAE,CAAC;QACrC,OAAO,OAAO,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;IACjD,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CACjC,UAAsB,EACtB,MAAc,IAAI,CAAC,GAAG,EAAE;IAExB,MAAM,SAAS,GAAG,UAAU,CAAC,SAAS,CAAC;IACvC,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC5B,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,CAAC,uBAAuB,CAAC,SAAS,CAAC,EAAE,CAAC;YACzE,OAAO,eAAe,CAAC;QACzB,CAAC;QACD,IAAI,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC;YAAE,OAAO,eAAe,CAAC;IAC1D,CAAC;IACD,MAAM,UAAU,GAAG,UAAU,CAAC,UAAU,CAAC;IACzC,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QAC7B,IACE,OAAO,UAAU,KAAK,QAAQ;YAC9B,CAAC,uBAAuB,CAAC,UAAU,CAAC,EACpC,CAAC;YACD,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,IAAI,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC;YAAE,OAAO,SAAS,CAAC;IACrD,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data Integrity proofs over JSON credentials, using the JCS cryptosuites.
|
|
3
|
+
*
|
|
4
|
+
* Implements `eddsa-jcs-2022` (Ed25519) and `ecdsa-jcs-2019` (ECDSA P-256 /
|
|
5
|
+
* P-384) from the W3C Data Integrity specs. The proof pipeline is:
|
|
6
|
+
*
|
|
7
|
+
* 1. Build a **proof configuration** (the proof object minus `proofValue`),
|
|
8
|
+
* copying the document's `@context`.
|
|
9
|
+
* 2. JCS-canonicalize the proof config and the document-without-proof, hash each
|
|
10
|
+
* with the cryptosuite's digest, and concatenate `proofConfigHash ‖ docHash`.
|
|
11
|
+
* 3. Sign (or verify) that concatenation with the verification key.
|
|
12
|
+
*
|
|
13
|
+
* The proof value is multibase base58-btc. ECDSA signatures use the IEEE P1363
|
|
14
|
+
* (`r ‖ s`) form Web Crypto produces, which is exactly what the cryptosuite
|
|
15
|
+
* expects. Signing/verification mirror the `@dwk/dpop` and `@dwk/http-signatures`
|
|
16
|
+
* posture — asymmetric only, an explicit cryptosuite allow-list, and the key is
|
|
17
|
+
* validated against the claimed cryptosuite.
|
|
18
|
+
*
|
|
19
|
+
* Pure aside from Web Crypto: plain-data in, plain-data out, no runtime bindings.
|
|
20
|
+
*
|
|
21
|
+
* @see https://www.w3.org/TR/vc-di-eddsa/
|
|
22
|
+
* @see https://www.w3.org/TR/vc-di-ecdsa/
|
|
23
|
+
*/
|
|
24
|
+
import { type JcsValue } from "./jcs";
|
|
25
|
+
/** A JSON object with string keys — a credential, proof, or proof config. */
|
|
26
|
+
export type JsonObject = {
|
|
27
|
+
[key: string]: JcsValue | undefined;
|
|
28
|
+
};
|
|
29
|
+
/** The Data Integrity cryptosuites this package implements. */
|
|
30
|
+
export type Cryptosuite = "eddsa-jcs-2022" | "ecdsa-jcs-2019";
|
|
31
|
+
/** Whether `value` names a supported cryptosuite. */
|
|
32
|
+
export declare function isSupportedCryptosuite(value: unknown): value is Cryptosuite;
|
|
33
|
+
type ComponentHash = "SHA-256" | "SHA-384";
|
|
34
|
+
interface AlgParams {
|
|
35
|
+
name: string;
|
|
36
|
+
namedCurve?: string;
|
|
37
|
+
hash?: string;
|
|
38
|
+
}
|
|
39
|
+
type SignVerifyParams = AlgParams;
|
|
40
|
+
/** A resolved signing key plus the cryptosuite parameters it implies. */
|
|
41
|
+
export interface Signer {
|
|
42
|
+
readonly key: CryptoKey;
|
|
43
|
+
readonly cryptosuite: Cryptosuite;
|
|
44
|
+
readonly componentHash: ComponentHash;
|
|
45
|
+
readonly params: SignVerifyParams;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Import a private signing key from a JWK and resolve the cryptosuite it
|
|
49
|
+
* implies: `OKP`/`Ed25519` → `eddsa-jcs-2022`; `EC`/`P-256`|`P-384` →
|
|
50
|
+
* `ecdsa-jcs-2019`. Throws for unsupported or non-private keys.
|
|
51
|
+
*/
|
|
52
|
+
export declare function importSigner(jwk: JsonWebKey): Promise<Signer>;
|
|
53
|
+
/** Options controlling how a Data Integrity proof is attached. */
|
|
54
|
+
export interface AddProofOptions {
|
|
55
|
+
/** The verification method id (`did:web:…#key`) clients resolve to verify. */
|
|
56
|
+
readonly verificationMethod: string;
|
|
57
|
+
/** Proof purpose. Defaults to `"assertionMethod"`. */
|
|
58
|
+
readonly proofPurpose?: string;
|
|
59
|
+
/** Proof creation time. A `Date` or an XSD dateTime string. Defaults to now. */
|
|
60
|
+
readonly created?: Date | string;
|
|
61
|
+
}
|
|
62
|
+
/** A document carrying its attached Data Integrity proof. */
|
|
63
|
+
export type SecuredDocument = JsonObject & {
|
|
64
|
+
proof: JsonObject;
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Attach a Data Integrity proof to `document`, returning a new secured document.
|
|
68
|
+
* Any existing `proof` on the input is excluded from the signed payload (a proof
|
|
69
|
+
* never signs over itself).
|
|
70
|
+
*/
|
|
71
|
+
export declare function addProof(document: JsonObject, signer: Signer, options: AddProofOptions): Promise<SecuredDocument>;
|
|
72
|
+
/** A verification method as published in a DID document. */
|
|
73
|
+
export interface VerificationMethod {
|
|
74
|
+
readonly id: string;
|
|
75
|
+
readonly type?: string;
|
|
76
|
+
readonly controller?: string;
|
|
77
|
+
readonly publicKeyMultibase?: string;
|
|
78
|
+
readonly publicKeyJwk?: JsonWebKey;
|
|
79
|
+
}
|
|
80
|
+
/** Resolve a verification method id to its document, or `undefined` if unknown. */
|
|
81
|
+
export type VerificationMethodResolver = (id: string) => VerificationMethod | undefined | Promise<VerificationMethod | undefined>;
|
|
82
|
+
/** Options for {@link verifyProof}. */
|
|
83
|
+
export interface VerifyProofOptions {
|
|
84
|
+
/** Resolves the proof's `verificationMethod` to its public key. */
|
|
85
|
+
readonly resolveVerificationMethod: VerificationMethodResolver;
|
|
86
|
+
/** Required proof purpose. Defaults to `"assertionMethod"`. */
|
|
87
|
+
readonly expectedProofPurpose?: string;
|
|
88
|
+
}
|
|
89
|
+
/** The outcome of verifying a document's Data Integrity proof(s). */
|
|
90
|
+
export interface VerifyProofResult {
|
|
91
|
+
readonly verified: boolean;
|
|
92
|
+
/** Stable, human-readable failure descriptions; empty when verified. */
|
|
93
|
+
readonly errors: readonly string[];
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Verify the Data Integrity proof(s) on `document`. A document with no proof, or
|
|
97
|
+
* any proof that fails, yields `verified: false` with the reasons collected in
|
|
98
|
+
* `errors`. Never throws — verification failures are returned, not raised.
|
|
99
|
+
*/
|
|
100
|
+
export declare function verifyProof(document: JsonObject, options: VerifyProofOptions): Promise<VerifyProofResult>;
|
|
101
|
+
export {};
|
|
102
|
+
//# sourceMappingURL=data-integrity.d.ts.map
|