@metalabel/dfos-protocol 0.49.0 → 0.51.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/README.md +8 -11
- package/dist/chain/index.d.ts +57 -21
- package/dist/chain/index.js +2 -2
- package/dist/{chunk-ZRA7FRBE.js → chunk-LMAPBTDW.js} +111 -33
- package/dist/{chunk-D4IZXFPM.js → chunk-T2YR5KWQ.js} +29 -12
- package/dist/credentials/index.d.ts +1 -1
- package/dist/credentials/index.js +1 -1
- package/dist/{dfos-credential-BtiYPqBT.d.ts → dfos-credential-CtG0_n4P.d.ts} +71 -20
- package/dist/index.d.ts +22 -1
- package/dist/index.js +8 -2
- package/package.json +1 -1
- package/schemas/credit-claim.v1.json +1 -1
- package/schemas/post.v1.json +1 -1
package/README.md
CHANGED
|
@@ -38,17 +38,14 @@ import { signKeyProof, verifyKeyProof } from '@metalabel/dfos-protocol/key-proof
|
|
|
38
38
|
|
|
39
39
|
## Specifications
|
|
40
40
|
|
|
41
|
-
| Document | Description
|
|
42
|
-
| ------------------------------------------------ |
|
|
43
|
-
| [PROTOCOL.md](../../specs/PROTOCOL.md) | Core protocol — chains, signatures, verification, test vectors
|
|
44
|
-
| [DID-METHOD.md](../../specs/DID-METHOD.md) | W3C DID method specification for `did:dfos`
|
|
45
|
-
| [CONTENT-MODEL.md](../../specs/CONTENT-MODEL.md) | Standard content schemas (post, profile)
|
|
46
|
-
| [CREDENTIALS.md](../../specs/CREDENTIALS.md) | UCAN-style authorization credentials for the DFOS protocol
|
|
47
|
-
| [
|
|
48
|
-
| [
|
|
49
|
-
| [SIWD.md](../../specs/SIWD.md) | Sign In With DFOS — cryptographic identity verification for third-party apps |
|
|
50
|
-
| [API-AUTH.md](../../specs/API-AUTH.md) | Proof-of-possession authentication for credential-gated HTTP APIs |
|
|
51
|
-
| [KEY-PROOF.md](../../specs/KEY-PROOF.md) | Challenge-bound, single-shot proof that a candidate key is held |
|
|
41
|
+
| Document | Description |
|
|
42
|
+
| ------------------------------------------------ | ------------------------------------------------------------------------------- |
|
|
43
|
+
| [PROTOCOL.md](../../specs/PROTOCOL.md) | Core protocol — chains, signatures, key possession, verification, test vectors |
|
|
44
|
+
| [DID-METHOD.md](../../specs/DID-METHOD.md) | W3C DID method specification for `did:dfos` |
|
|
45
|
+
| [CONTENT-MODEL.md](../../specs/CONTENT-MODEL.md) | Standard content schemas (post, profile) and verifiable attribution |
|
|
46
|
+
| [CREDENTIALS.md](../../specs/CREDENTIALS.md) | UCAN-style authorization credentials for the DFOS protocol |
|
|
47
|
+
| [RELAY.md](../../specs/RELAY.md) | The relay HTTP surface, including the sign-request envelope and signing mailbox |
|
|
48
|
+
| [INTEGRATIONS.md](../../specs/INTEGRATIONS.md) | Sign in, API authentication, origin binding, and key ceremonies |
|
|
52
49
|
|
|
53
50
|
Release history lives at https://github.com/metalabel/dfos/releases.
|
|
54
51
|
|
package/dist/chain/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { I as IdentityOperation,
|
|
2
|
-
export { A as ARTIFACT_CID_ANCHOR_RE, C as CONTENT_ID_ANCHOR_RE, f as CreditClaimPayload, g as DeclaredKeyState, h as Iso8601, M as MAX_ARTIFACT_PAYLOAD_SIZE, j as MAX_CREDIT_CLAIM_SIZE, k as MAX_KEY_PROOFS, l as MAX_OPERATION_SIZE, m as MAX_SERVICES_ENTRIES, n as MAX_SERVICES_PAYLOAD_SIZE, o as MAX_SIGN_REQUEST_PAYLOAD_SIZE, p as MAX_SIGN_REQUEST_SIZE, q as MultikeyPublicKey,
|
|
1
|
+
import { I as IdentityOperation, v as Signer, x as VerifiedIdentity, S as ServiceEntry, c as ContentOperation, R as ResolvedIdentity, r as RevocationChecker, d as CountersignPayload, a as ArtifactPayload } from '../dfos-credential-CtG0_n4P.js';
|
|
2
|
+
export { A as ARTIFACT_CID_ANCHOR_RE, C as CONTENT_ID_ANCHOR_RE, f as CreditClaimPayload, g as DeclaredKeyState, h as Iso8601, M as MAX_ARTIFACT_PAYLOAD_SIZE, j as MAX_CREDIT_CLAIM_SIZE, k as MAX_KEY_PROOFS, l as MAX_OPERATION_SIZE, m as MAX_SERVICES_ENTRIES, n as MAX_SERVICES_PAYLOAD_SIZE, o as MAX_SIGN_REQUEST_PAYLOAD_SIZE, p as MAX_SIGN_REQUEST_SIZE, q as MultikeyPublicKey, s as RevocationPayload, t as ServicesArray, u as SignRequestPayload, y as VoidKeyMembership, G as parseProtocolTimestampUnix } from '../dfos-credential-CtG0_n4P.js';
|
|
3
3
|
import 'zod';
|
|
4
4
|
|
|
5
5
|
/** Ed25519 public key multicodec value */
|
|
@@ -54,13 +54,26 @@ declare const signIdentityOperation: (input: {
|
|
|
54
54
|
* Verify a log of JWS identity operations and derive the identity
|
|
55
55
|
*
|
|
56
56
|
* Walks the chain from genesis, verifying signatures and chain integrity, and
|
|
57
|
-
* folds the possession proofs alongside. Returns the
|
|
58
|
-
*
|
|
57
|
+
* folds the possession proofs alongside. Returns the verified identity state:
|
|
58
|
+
* EFFECTIVE key arrays, with the declared arrays and the void memberships
|
|
59
59
|
* beside them.
|
|
60
|
+
*
|
|
61
|
+
* THE BASIS SELECTS WHICH STATE COMES BACK, never how much of the log is
|
|
62
|
+
* verified. With `asOf` set, the whole log is walked and validated exactly as
|
|
63
|
+
* without it, and the state returned is the one the last operation dated at or
|
|
64
|
+
* before the basis folds to — the identity's state as of that instant
|
|
65
|
+
* (PROTOCOL, Time basis). The comparison is byte-wise on the `createdAt`
|
|
66
|
+
* strings, per PROTOCOL's Comparison basis. A basis earlier than genesis has no
|
|
67
|
+
* state to name and throws, and so does an empty basis.
|
|
60
68
|
*/
|
|
61
69
|
declare const verifyIdentityChain: (input: {
|
|
62
70
|
didPrefix: string;
|
|
63
71
|
log: string[];
|
|
72
|
+
/**
|
|
73
|
+
* Basis time, in the `createdAt` grammar. Omitted = the chain head, which is
|
|
74
|
+
* the state as of now.
|
|
75
|
+
*/
|
|
76
|
+
asOf?: string;
|
|
64
77
|
}) => Promise<VerifiedIdentity>;
|
|
65
78
|
/**
|
|
66
79
|
* Verify a single new operation against already-verified identity state
|
|
@@ -70,10 +83,13 @@ declare const verifyIdentityChain: (input: {
|
|
|
70
83
|
* verified genesis). This function performs one signature verification and one
|
|
71
84
|
* state transition — constant time regardless of chain length.
|
|
72
85
|
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
86
|
+
* KEY-ID CONSISTENCY IS CHECKED HERE, on the same terms as the full walk. The
|
|
87
|
+
* trusted state carries `seenKeys` — the id-to-material binding the walk already
|
|
88
|
+
* enforced — so an operation that re-points an existing key id at new material
|
|
89
|
+
* is REJECTED on this path exactly as a replay of the same two operations would
|
|
90
|
+
* reject it. Without that the fast path would accept a chain its own
|
|
91
|
+
* re-verification refuses, and a relay's linear path is the path almost every
|
|
92
|
+
* operation takes.
|
|
77
93
|
*
|
|
78
94
|
* THE POSSESSION FOLD RUNS HERE TOO, and it needs both halves of the trusted
|
|
79
95
|
* state: the EFFECTIVE arrays (to know what an introduction is a transition out
|
|
@@ -164,8 +180,11 @@ declare const signContentOperation: (input: {
|
|
|
164
180
|
*/
|
|
165
181
|
declare const verifyContentChain: (input: {
|
|
166
182
|
log: string[];
|
|
167
|
-
/**
|
|
168
|
-
|
|
183
|
+
/**
|
|
184
|
+
* Resolve a kid (DID URL) to the raw Ed25519 public key bytes, in the signing
|
|
185
|
+
* identity's state as of `basis` — each operation's own `createdAt`.
|
|
186
|
+
*/
|
|
187
|
+
resolveKey: (kid: string, basis?: string) => Promise<Uint8Array>;
|
|
169
188
|
/**
|
|
170
189
|
* Enforce creator-sovereignty authorization. When true, non-creator signers
|
|
171
190
|
* must include a DFOS credential in the operation's `authorization` field
|
|
@@ -173,10 +192,11 @@ declare const verifyContentChain: (input: {
|
|
|
173
192
|
*/
|
|
174
193
|
enforceAuthorization?: boolean;
|
|
175
194
|
/**
|
|
176
|
-
* Resolve a DID to a
|
|
177
|
-
* is true, as credential verification needs identity
|
|
195
|
+
* Resolve a DID to a ResolvedIdentity as of `basis`. Required when
|
|
196
|
+
* `enforceAuthorization` is true, as credential verification needs identity
|
|
197
|
+
* resolution.
|
|
178
198
|
*/
|
|
179
|
-
resolveIdentity?: (did: string) => Promise<
|
|
199
|
+
resolveIdentity?: (did: string, basis?: string) => Promise<ResolvedIdentity | undefined>;
|
|
180
200
|
/**
|
|
181
201
|
* Check whether a credential (leaf or parent) has been revoked. Called with
|
|
182
202
|
* `asOfUnix` = each operation's own `createdAt`, so a fold of committed
|
|
@@ -198,12 +218,15 @@ declare const verifyContentExtensionFromTrustedState: (input: {
|
|
|
198
218
|
lastCreatedAt: string;
|
|
199
219
|
/** The new JWS operation to verify */
|
|
200
220
|
newOp: string;
|
|
201
|
-
/**
|
|
202
|
-
|
|
221
|
+
/**
|
|
222
|
+
* Resolve a kid (DID URL) to the raw Ed25519 public key bytes, in the signing
|
|
223
|
+
* identity's state as of `basis` — the new operation's own `createdAt`.
|
|
224
|
+
*/
|
|
225
|
+
resolveKey: (kid: string, basis?: string) => Promise<Uint8Array>;
|
|
203
226
|
/** Enforce creator-sovereignty authorization (see verifyContentChain) */
|
|
204
227
|
enforceAuthorization?: boolean;
|
|
205
|
-
/** Resolve a DID to a
|
|
206
|
-
resolveIdentity?: (did: string) => Promise<
|
|
228
|
+
/** Resolve a DID to a ResolvedIdentity as of `basis`. Required when enforceAuthorization is true. */
|
|
229
|
+
resolveIdentity?: (did: string, basis?: string) => Promise<ResolvedIdentity | undefined>;
|
|
207
230
|
/**
|
|
208
231
|
* Check whether a credential (leaf or parent) has been revoked. Called with
|
|
209
232
|
* `asOfUnix` = the new operation's own `createdAt`. See `RevocationChecker`.
|
|
@@ -242,10 +265,15 @@ declare const signCountersignature: (input: {
|
|
|
242
265
|
* Checks: valid signature, CID integrity, payload schema. Does NOT check
|
|
243
266
|
* whether the target exists or whether the witness differs from the target
|
|
244
267
|
* author — those are relay-level semantic checks.
|
|
268
|
+
*
|
|
269
|
+
* The countersignature's own `createdAt` is the basis: a committed statement
|
|
270
|
+
* resolves its signer in the state that held when it was signed (PROTOCOL, Time
|
|
271
|
+
* basis).
|
|
245
272
|
*/
|
|
246
273
|
declare const verifyCountersignature: (input: {
|
|
247
274
|
jwsToken: string;
|
|
248
|
-
|
|
275
|
+
/** Resolve a kid to key bytes in the signer's state as of `basis`. */
|
|
276
|
+
resolveKey: (kid: string, basis?: string) => Promise<Uint8Array>;
|
|
249
277
|
}) => Promise<VerifiedCountersignature>;
|
|
250
278
|
|
|
251
279
|
interface VerifiedArtifact {
|
|
@@ -267,10 +295,14 @@ declare const signArtifact: (input: {
|
|
|
267
295
|
}>;
|
|
268
296
|
/**
|
|
269
297
|
* Verify an artifact JWS — signature, CID, payload schema, size limit
|
|
298
|
+
*
|
|
299
|
+
* The artifact's own `createdAt` is the basis: a committed statement resolves
|
|
300
|
+
* its signer in the state that held when it was signed (PROTOCOL, Time basis).
|
|
270
301
|
*/
|
|
271
302
|
declare const verifyArtifact: (input: {
|
|
272
303
|
jwsToken: string;
|
|
273
|
-
|
|
304
|
+
/** Resolve a kid to key bytes in the signer's state as of `basis`. */
|
|
305
|
+
resolveKey: (kid: string, basis?: string) => Promise<Uint8Array>;
|
|
274
306
|
}) => Promise<VerifiedArtifact>;
|
|
275
307
|
|
|
276
308
|
interface VerifiedRevocation {
|
|
@@ -299,10 +331,14 @@ declare const signRevocation: (input: {
|
|
|
299
331
|
}>;
|
|
300
332
|
/**
|
|
301
333
|
* Verify a revocation JWS — signature, CID, payload schema, signer match
|
|
334
|
+
*
|
|
335
|
+
* The revocation's own `createdAt` is the basis: a committed statement resolves
|
|
336
|
+
* its signer in the state that held when it was signed (PROTOCOL, Time basis).
|
|
302
337
|
*/
|
|
303
338
|
declare const verifyRevocation: (input: {
|
|
304
339
|
jwsToken: string;
|
|
305
|
-
|
|
340
|
+
/** Resolve a kid to key bytes in the signer's state as of `basis`. */
|
|
341
|
+
resolveKey: (kid: string, basis?: string) => Promise<Uint8Array>;
|
|
306
342
|
}) => Promise<VerifiedRevocation>;
|
|
307
343
|
|
|
308
344
|
interface VerifiedCreditClaim {
|
|
@@ -322,7 +358,7 @@ interface VerifiedCreditClaim {
|
|
|
322
358
|
claimCID: string;
|
|
323
359
|
}
|
|
324
360
|
/**
|
|
325
|
-
* The four states a `credits[]` entry resolves to (see `specs/
|
|
361
|
+
* The four states a `credits[]` entry resolves to (see `specs/CONTENT-MODEL.md`).
|
|
326
362
|
*
|
|
327
363
|
* `invalid` and `unverifiable` are deliberately distinct and MUST NOT be
|
|
328
364
|
* collapsed: `invalid` means "checked and failed" (a positive signal that
|
package/dist/chain/index.js
CHANGED
|
@@ -52,8 +52,8 @@ import {
|
|
|
52
52
|
verifyIdentityExtensionFromTrustedState,
|
|
53
53
|
verifyRevocation,
|
|
54
54
|
verifySignRequest
|
|
55
|
-
} from "../chunk-
|
|
56
|
-
import "../chunk-
|
|
55
|
+
} from "../chunk-LMAPBTDW.js";
|
|
56
|
+
import "../chunk-T2YR5KWQ.js";
|
|
57
57
|
import "../chunk-JVSC67DC.js";
|
|
58
58
|
import {
|
|
59
59
|
ED25519_PRIV_MULTICODEC,
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
MAX_CREDENTIAL_SIZE,
|
|
3
|
+
carryDependencyMissing,
|
|
3
4
|
decodeDFOSCredentialUnsafe,
|
|
5
|
+
markDependencyMissing,
|
|
4
6
|
matchesResource,
|
|
5
7
|
verifyDFOSCredential,
|
|
6
8
|
verifyDelegationChain
|
|
7
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-T2YR5KWQ.js";
|
|
8
10
|
import {
|
|
9
11
|
KEY_ADD_JWS_TYP,
|
|
10
12
|
KEY_ROLES,
|
|
@@ -100,7 +102,7 @@ var IdentityUpdate = z.looseObject({
|
|
|
100
102
|
createdAt: Iso8601,
|
|
101
103
|
/**
|
|
102
104
|
* POSSESSION PROOFS FOR THE KEYS THIS OPERATION INTRODUCES — compact key-proof
|
|
103
|
-
* JWS strings (specs/
|
|
105
|
+
* JWS strings (specs/PROTOCOL.md § Key possession), each signed by the key it speaks for.
|
|
104
106
|
*
|
|
105
107
|
* Optional, and optional in the CID-neutral sense: omitting it encodes
|
|
106
108
|
* identically to an operation that never had it (undefined strips under
|
|
@@ -186,7 +188,31 @@ var VerifiedIdentity = z.strictObject({
|
|
|
186
188
|
* re-deriving it from the raw log under a declared-state rule that quietly
|
|
187
189
|
* disagrees with this one.
|
|
188
190
|
*/
|
|
189
|
-
provedKeys: DeclaredKeyState.optional()
|
|
191
|
+
provedKeys: DeclaredKeyState.optional(),
|
|
192
|
+
/**
|
|
193
|
+
* HAS-EVER-BEEN-DECLARED, as a key-id-to-material binding: one entry per key
|
|
194
|
+
* id the chain has ever written, carrying the Multikey that id went in with,
|
|
195
|
+
* in the order the walk first saw it.
|
|
196
|
+
*
|
|
197
|
+
* A KEY ID IS BOUND TO ONE KEY FOR THE LIFE OF A CHAIN. The full walk enforces
|
|
198
|
+
* that chain-wide and rejects the operation that breaks it — the id is a
|
|
199
|
+
* stable name, so re-pointing it at new material would silently re-aim every
|
|
200
|
+
* artifact that ever referenced it. The incremental extension verifier has no
|
|
201
|
+
* log to re-scan, so it needs the binding handed to it, and this member is how
|
|
202
|
+
* it arrives: with it, the fast path returns the SAME verdict as a full replay
|
|
203
|
+
* on the same operation, which is the property that keeps a relay's linear
|
|
204
|
+
* path from accepting a chain its own re-verification would refuse.
|
|
205
|
+
*
|
|
206
|
+
* Broader than `declared` and than `provedKeys` on purpose. The rule covers
|
|
207
|
+
* every id the chain wrote, including one declared into a void membership and
|
|
208
|
+
* later dropped — neither of the other two members remembers that id, and it
|
|
209
|
+
* is exactly the id an attacker would reuse.
|
|
210
|
+
*
|
|
211
|
+
* Optional because a caller may hand the extension verifier a hand-built or
|
|
212
|
+
* older state. Absent, the binding is read off `declared` and `provedKeys`,
|
|
213
|
+
* which is complete for any chain that never dropped an unproved key id.
|
|
214
|
+
*/
|
|
215
|
+
seenKeys: z.array(MultikeyPublicKey).optional()
|
|
190
216
|
});
|
|
191
217
|
var ContentCreate = z.looseObject({
|
|
192
218
|
version: z.literal(1),
|
|
@@ -331,7 +357,9 @@ var unionProved = (into, next) => {
|
|
|
331
357
|
return merged;
|
|
332
358
|
};
|
|
333
359
|
var foldEffectiveKeyState = (input) => {
|
|
334
|
-
const carried = (role, key) => input.priorEffective[ROLE_ARRAY[role]].some(
|
|
360
|
+
const carried = (role, key) => input.priorEffective[ROLE_ARRAY[role]].some(
|
|
361
|
+
(prior) => prior.id === key.id && prior.type === key.type && prior.publicKeyMultibase === key.publicKeyMultibase
|
|
362
|
+
);
|
|
335
363
|
const introduced = /* @__PURE__ */ new Map();
|
|
336
364
|
for (const role of KEY_ROLES) {
|
|
337
365
|
for (const key of input.declared[ROLE_ARRAY[role]]) {
|
|
@@ -377,6 +405,26 @@ var foldEffectiveKeyState = (input) => {
|
|
|
377
405
|
}
|
|
378
406
|
return { effective, voidKeys };
|
|
379
407
|
};
|
|
408
|
+
var admitKeyMaterial = (index, keys) => {
|
|
409
|
+
for (const key of keys) {
|
|
410
|
+
const existing = index.get(key.id);
|
|
411
|
+
if (!existing) {
|
|
412
|
+
index.set(key.id, key);
|
|
413
|
+
} else if (existing.publicKeyMultibase !== key.publicKeyMultibase || existing.type !== key.type) {
|
|
414
|
+
throw new Error(`key ${key.id} type or public key inconsistency`);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
var keyMaterialIndex = (keys) => {
|
|
419
|
+
const index = /* @__PURE__ */ new Map();
|
|
420
|
+
for (const key of keys) if (!index.has(key.id)) index.set(key.id, key);
|
|
421
|
+
return index;
|
|
422
|
+
};
|
|
423
|
+
var flatKeys = (state) => [
|
|
424
|
+
...state.authKeys,
|
|
425
|
+
...state.assertKeys,
|
|
426
|
+
...state.controllerKeys
|
|
427
|
+
];
|
|
380
428
|
var assertSingleKeyGenesis = (op) => {
|
|
381
429
|
if (op.authKeys.length !== 1 || op.assertKeys.length !== 1 || op.controllerKeys.length !== 1) {
|
|
382
430
|
throw new Error("create must declare exactly one key in each of auth, assert and controller");
|
|
@@ -405,6 +453,7 @@ var signIdentityOperation = async (input) => {
|
|
|
405
453
|
};
|
|
406
454
|
var verifyIdentityChain = async (input) => {
|
|
407
455
|
if (input.log.length === 0) throw new Error("log must have at least one operation");
|
|
456
|
+
if (input.asOf === "") throw new Error("basis must not be empty");
|
|
408
457
|
const state = {
|
|
409
458
|
did: void 0,
|
|
410
459
|
isDeleted: false,
|
|
@@ -420,6 +469,7 @@ var verifyIdentityChain = async (input) => {
|
|
|
420
469
|
services: [],
|
|
421
470
|
seenKeys: /* @__PURE__ */ new Map()
|
|
422
471
|
};
|
|
472
|
+
let asOfState;
|
|
423
473
|
for (const [idx, jwsToken] of input.log.entries()) {
|
|
424
474
|
const decoded = decodeJwsUnsafe(jwsToken);
|
|
425
475
|
if (!decoded) throw new Error(`log[${idx}]: failed to decode JWS`);
|
|
@@ -470,19 +520,15 @@ var verifyIdentityChain = async (input) => {
|
|
|
470
520
|
}
|
|
471
521
|
}
|
|
472
522
|
if (op.type === "create" || op.type === "update") {
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
state.seenKeys.set(k.id, k);
|
|
483
|
-
} else if (existing.publicKeyMultibase !== k.publicKeyMultibase || existing.type !== k.type) {
|
|
484
|
-
throw new Error(`log[${idx}]: key ${k.id} type or public key inconsistency`);
|
|
485
|
-
}
|
|
523
|
+
try {
|
|
524
|
+
admitKeyMaterial(state.seenKeys, [
|
|
525
|
+
...flatKeys(state.declared),
|
|
526
|
+
...op.authKeys,
|
|
527
|
+
...op.assertKeys,
|
|
528
|
+
...op.controllerKeys
|
|
529
|
+
]);
|
|
530
|
+
} catch (e) {
|
|
531
|
+
throw new Error(`log[${idx}]: ${e.message}`);
|
|
486
532
|
}
|
|
487
533
|
[op.authKeys, op.assertKeys, op.controllerKeys].forEach((keys) => {
|
|
488
534
|
const set = new Set(keys.map((k) => k.id));
|
|
@@ -576,8 +622,26 @@ var verifyIdentityChain = async (input) => {
|
|
|
576
622
|
state.isDeleted = false;
|
|
577
623
|
break;
|
|
578
624
|
}
|
|
625
|
+
if (input.asOf !== void 0 && op.createdAt <= input.asOf) {
|
|
626
|
+
asOfState = {
|
|
627
|
+
did,
|
|
628
|
+
isDeleted: state.isDeleted,
|
|
629
|
+
authKeys: state.effective.authKeys,
|
|
630
|
+
assertKeys: state.effective.assertKeys,
|
|
631
|
+
controllerKeys: state.effective.controllerKeys,
|
|
632
|
+
services: state.services,
|
|
633
|
+
declared: state.declared,
|
|
634
|
+
voidKeys: state.voidKeys,
|
|
635
|
+
provedKeys: state.provedKeys,
|
|
636
|
+
seenKeys: [...state.seenKeys.values()]
|
|
637
|
+
};
|
|
638
|
+
}
|
|
579
639
|
}
|
|
580
640
|
if (!state.did) throw new Error("did is not set");
|
|
641
|
+
if (input.asOf !== void 0) {
|
|
642
|
+
if (!asOfState) throw new Error(`identity has no state as of ${input.asOf}`);
|
|
643
|
+
return asOfState;
|
|
644
|
+
}
|
|
581
645
|
return {
|
|
582
646
|
did: state.did,
|
|
583
647
|
isDeleted: state.isDeleted,
|
|
@@ -588,7 +652,10 @@ var verifyIdentityChain = async (input) => {
|
|
|
588
652
|
services: state.services,
|
|
589
653
|
declared: state.declared,
|
|
590
654
|
voidKeys: state.voidKeys,
|
|
591
|
-
provedKeys: state.provedKeys
|
|
655
|
+
provedKeys: state.provedKeys,
|
|
656
|
+
// The id-to-material binding the walk enforced, handed on so an incremental
|
|
657
|
+
// extension of this state can enforce the identical rule without the log.
|
|
658
|
+
seenKeys: [...state.seenKeys.values()]
|
|
592
659
|
};
|
|
593
660
|
};
|
|
594
661
|
var verifyIdentityExtensionFromTrustedState = async (input) => {
|
|
@@ -596,6 +663,10 @@ var verifyIdentityExtensionFromTrustedState = async (input) => {
|
|
|
596
663
|
const priorEffective = keyStateOf(currentState);
|
|
597
664
|
const priorDeclared = currentState.declared ?? priorEffective;
|
|
598
665
|
const priorProved = currentState.provedKeys ?? priorEffective;
|
|
666
|
+
const seenKeys = keyMaterialIndex(
|
|
667
|
+
currentState.seenKeys ?? [...flatKeys(priorDeclared), ...flatKeys(priorProved)]
|
|
668
|
+
);
|
|
669
|
+
const priorSeenKeys = [...seenKeys.values()];
|
|
599
670
|
const decoded = decodeJwsUnsafe(newOp);
|
|
600
671
|
if (!decoded) throw new Error("failed to decode JWS");
|
|
601
672
|
const result = IdentityOperation.safeParse(decoded.payload);
|
|
@@ -651,6 +722,7 @@ var verifyIdentityExtensionFromTrustedState = async (input) => {
|
|
|
651
722
|
throw new Error("invalid signature");
|
|
652
723
|
}
|
|
653
724
|
if (op.type === "update") {
|
|
725
|
+
admitKeyMaterial(seenKeys, [...op.authKeys, ...op.assertKeys, ...op.controllerKeys]);
|
|
654
726
|
[op.authKeys, op.assertKeys, op.controllerKeys].forEach((keys) => {
|
|
655
727
|
const set = new Set(keys.map((k) => k.id));
|
|
656
728
|
if (set.size !== keys.length) {
|
|
@@ -680,7 +752,8 @@ var verifyIdentityExtensionFromTrustedState = async (input) => {
|
|
|
680
752
|
services: op.services ?? [],
|
|
681
753
|
declared,
|
|
682
754
|
voidKeys: folded.voidKeys,
|
|
683
|
-
provedKeys: unionProved(priorProved, folded.effective)
|
|
755
|
+
provedKeys: unionProved(priorProved, folded.effective),
|
|
756
|
+
seenKeys: [...seenKeys.values()]
|
|
684
757
|
};
|
|
685
758
|
}
|
|
686
759
|
// delete and restore introduce nothing, so both key states — and the void
|
|
@@ -695,7 +768,8 @@ var verifyIdentityExtensionFromTrustedState = async (input) => {
|
|
|
695
768
|
services: currentState.services,
|
|
696
769
|
declared: priorDeclared,
|
|
697
770
|
voidKeys: currentState.voidKeys ?? [],
|
|
698
|
-
provedKeys: priorProved
|
|
771
|
+
provedKeys: priorProved,
|
|
772
|
+
seenKeys: priorSeenKeys
|
|
699
773
|
};
|
|
700
774
|
case "restore":
|
|
701
775
|
return {
|
|
@@ -707,7 +781,8 @@ var verifyIdentityExtensionFromTrustedState = async (input) => {
|
|
|
707
781
|
services: currentState.services,
|
|
708
782
|
declared: priorDeclared,
|
|
709
783
|
voidKeys: currentState.voidKeys ?? [],
|
|
710
|
-
provedKeys: priorProved
|
|
784
|
+
provedKeys: priorProved,
|
|
785
|
+
seenKeys: priorSeenKeys
|
|
711
786
|
};
|
|
712
787
|
}
|
|
713
788
|
})();
|
|
@@ -748,7 +823,7 @@ var verifyOperationAuthorization = async (input) => {
|
|
|
748
823
|
const opCreatedAtUnix = Math.floor(new Date(input.createdAt).getTime() / 1e3);
|
|
749
824
|
const credential = await verifyDFOSCredential(input.authorization, {
|
|
750
825
|
resolveIdentity: input.resolveIdentity,
|
|
751
|
-
|
|
826
|
+
basis: input.createdAt
|
|
752
827
|
});
|
|
753
828
|
if (input.isRevoked && await input.isRevoked(credential.iss, credential.credentialCID, opCreatedAtUnix)) {
|
|
754
829
|
throw new Error("credential is revoked");
|
|
@@ -756,8 +831,8 @@ var verifyOperationAuthorization = async (input) => {
|
|
|
756
831
|
await verifyDelegationChain(credential, {
|
|
757
832
|
resolveIdentity: input.resolveIdentity,
|
|
758
833
|
rootDID: input.creatorDID,
|
|
759
|
-
|
|
760
|
-
...input.isRevoked ? { isRevoked: input.isRevoked
|
|
834
|
+
basis: input.createdAt,
|
|
835
|
+
...input.isRevoked ? { isRevoked: input.isRevoked } : {}
|
|
761
836
|
});
|
|
762
837
|
if (credential.aud !== "*" && credential.aud !== input.operationDID) {
|
|
763
838
|
throw new Error(
|
|
@@ -819,7 +894,7 @@ var verifyContentChain = async (input) => {
|
|
|
819
894
|
if (kidDid !== op.did) {
|
|
820
895
|
throw new Error(`log[${idx}]: kid DID does not match operation did`);
|
|
821
896
|
}
|
|
822
|
-
const publicKey = await input.resolveKey(kid);
|
|
897
|
+
const publicKey = await input.resolveKey(kid, op.createdAt);
|
|
823
898
|
try {
|
|
824
899
|
verifyJws({ token: jwsToken, publicKey });
|
|
825
900
|
} catch {
|
|
@@ -846,7 +921,10 @@ var verifyContentChain = async (input) => {
|
|
|
846
921
|
});
|
|
847
922
|
} catch (err) {
|
|
848
923
|
const message = err instanceof Error ? err.message : "unknown error";
|
|
849
|
-
throw
|
|
924
|
+
throw carryDependencyMissing(
|
|
925
|
+
err,
|
|
926
|
+
new Error(`log[${idx}]: authorization verification failed: ${message}`)
|
|
927
|
+
);
|
|
850
928
|
}
|
|
851
929
|
}
|
|
852
930
|
const encoded = await dagCborCanonicalEncode(op);
|
|
@@ -926,7 +1004,7 @@ var verifyContentExtensionFromTrustedState = async (input) => {
|
|
|
926
1004
|
if (kidDid !== op.did) {
|
|
927
1005
|
throw new Error("kid DID does not match operation did");
|
|
928
1006
|
}
|
|
929
|
-
const publicKey = await resolveKey(kid);
|
|
1007
|
+
const publicKey = await resolveKey(kid, op.createdAt);
|
|
930
1008
|
try {
|
|
931
1009
|
verifyJws({ token: newOp, publicKey });
|
|
932
1010
|
} catch {
|
|
@@ -951,7 +1029,7 @@ var verifyContentExtensionFromTrustedState = async (input) => {
|
|
|
951
1029
|
});
|
|
952
1030
|
} catch (err) {
|
|
953
1031
|
const message = err instanceof Error ? err.message : "unknown error";
|
|
954
|
-
throw new Error(`authorization verification failed: ${message}`);
|
|
1032
|
+
throw carryDependencyMissing(err, new Error(`authorization verification failed: ${message}`));
|
|
955
1033
|
}
|
|
956
1034
|
}
|
|
957
1035
|
const encoded = await dagCborCanonicalEncode(op);
|
|
@@ -1004,7 +1082,7 @@ var verifyCountersignature = async (input) => {
|
|
|
1004
1082
|
if (kidDid !== payload.did) {
|
|
1005
1083
|
throw new Error("countersignature kid DID does not match payload did");
|
|
1006
1084
|
}
|
|
1007
|
-
const publicKey = await input.resolveKey(kid);
|
|
1085
|
+
const publicKey = await input.resolveKey(kid, payload.createdAt);
|
|
1008
1086
|
try {
|
|
1009
1087
|
verifyJws({ token: input.jwsToken, publicKey });
|
|
1010
1088
|
} catch {
|
|
@@ -1057,7 +1135,7 @@ var verifyArtifact = async (input) => {
|
|
|
1057
1135
|
if (kidDid !== payload.did) {
|
|
1058
1136
|
throw new Error("artifact kid DID does not match payload did");
|
|
1059
1137
|
}
|
|
1060
|
-
const publicKey = await input.resolveKey(kid);
|
|
1138
|
+
const publicKey = await input.resolveKey(kid, payload.createdAt);
|
|
1061
1139
|
try {
|
|
1062
1140
|
verifyJws({ token: input.jwsToken, publicKey });
|
|
1063
1141
|
} catch {
|
|
@@ -1114,7 +1192,7 @@ var verifyRevocation = async (input) => {
|
|
|
1114
1192
|
if (kidDid !== payload.did) {
|
|
1115
1193
|
throw new Error("revocation kid DID does not match payload did");
|
|
1116
1194
|
}
|
|
1117
|
-
const publicKey = await input.resolveKey(kid);
|
|
1195
|
+
const publicKey = await input.resolveKey(kid, payload.createdAt);
|
|
1118
1196
|
try {
|
|
1119
1197
|
verifyJws({ token: input.jwsToken, publicKey });
|
|
1120
1198
|
} catch {
|
|
@@ -1151,7 +1229,7 @@ var resolveKeyFromIdentity = (identity, kid) => {
|
|
|
1151
1229
|
const allKeys = [...identity.authKeys, ...identity.assertKeys, ...identity.controllerKeys];
|
|
1152
1230
|
const key = allKeys.find((k) => k.id === keyId);
|
|
1153
1231
|
if (!key) {
|
|
1154
|
-
throw invalid(`key ${keyId} not found on identity ${identity.did}`);
|
|
1232
|
+
throw markDependencyMissing(invalid(`key ${keyId} not found on identity ${identity.did}`));
|
|
1155
1233
|
}
|
|
1156
1234
|
const { keyBytes } = decodeMultikey(key.publicKeyMultibase);
|
|
1157
1235
|
return keyBytes;
|
|
@@ -1241,7 +1319,7 @@ var verifyCreditClaim = async (jwsToken, options) => {
|
|
|
1241
1319
|
throw unverifiable(`could not resolve claimant identity ${payload.did}: ${detail}`);
|
|
1242
1320
|
}
|
|
1243
1321
|
if (!identity) {
|
|
1244
|
-
throw unverifiable(`claimant identity not found: ${payload.did}`);
|
|
1322
|
+
throw markDependencyMissing(unverifiable(`claimant identity not found: ${payload.did}`));
|
|
1245
1323
|
}
|
|
1246
1324
|
const publicKey = resolveKeyFromIdentity(identity, kid);
|
|
1247
1325
|
try {
|
|
@@ -364,19 +364,32 @@ var verifyProofEnvelope = async (input, shape, resolvePresenter) => {
|
|
|
364
364
|
var verifyIdentityProofEnvelope = (input, resolvePresenter) => verifyProofEnvelope(input, IDENTITY_PROOF_SHAPE, resolvePresenter);
|
|
365
365
|
var verifyRequestProofEnvelope = (input, resolvePresenter) => verifyProofEnvelope(input, REQUEST_PROOF_SHAPE, resolvePresenter);
|
|
366
366
|
|
|
367
|
+
// src/dependency.ts
|
|
368
|
+
var markDependencyMissing = (err) => Object.assign(err, { dependencyMissing: true });
|
|
369
|
+
var isDependencyMissing = (err) => err instanceof Error && err.dependencyMissing === true;
|
|
370
|
+
var carryDependencyMissing = (cause, wrapped) => isDependencyMissing(cause) ? markDependencyMissing(wrapped) : wrapped;
|
|
371
|
+
|
|
367
372
|
// src/credentials/dfos-credential.ts
|
|
368
|
-
var resolveKeyFromIdentity = (identity, kid) => {
|
|
373
|
+
var resolveKeyFromIdentity = (identity, kid, determinate) => {
|
|
369
374
|
const hashIdx = kid.indexOf("#");
|
|
370
375
|
if (hashIdx < 0) throw new CredentialVerificationError("kid must be a DID URL");
|
|
371
376
|
const keyId = kid.substring(hashIdx + 1);
|
|
372
377
|
const allKeys = [...identity.authKeys, ...identity.assertKeys, ...identity.controllerKeys];
|
|
373
378
|
const key = allKeys.find((k) => k.id === keyId);
|
|
374
379
|
if (!key) {
|
|
375
|
-
|
|
380
|
+
const miss = new CredentialVerificationError(
|
|
381
|
+
`unknown key ${keyId} on identity ${identity.did}`
|
|
382
|
+
);
|
|
383
|
+
throw determinate ? miss : markDependencyMissing(miss);
|
|
376
384
|
}
|
|
377
385
|
const { keyBytes } = decodeMultikey(key.publicKeyMultibase);
|
|
378
386
|
return keyBytes;
|
|
379
387
|
};
|
|
388
|
+
var basisUnixSeconds = (basis) => {
|
|
389
|
+
const ms = Date.parse(basis);
|
|
390
|
+
if (Number.isNaN(ms)) throw new CredentialVerificationError(`invalid basis time: ${basis}`);
|
|
391
|
+
return Math.floor(ms / 1e3);
|
|
392
|
+
};
|
|
380
393
|
var createDFOSCredential = async (options) => {
|
|
381
394
|
const kid = `${options.issuerDID}#${options.keyId}`;
|
|
382
395
|
const now = options.iat ?? Math.floor(Date.now() / 1e3);
|
|
@@ -428,14 +441,16 @@ var verifyDFOSCredential = async (jwsToken, options) => {
|
|
|
428
441
|
if (kidDid !== payload.iss) {
|
|
429
442
|
throw new CredentialVerificationError("credential kid DID does not match iss");
|
|
430
443
|
}
|
|
431
|
-
const identity = await options.resolveIdentity(payload.iss);
|
|
444
|
+
const identity = await options.resolveIdentity(payload.iss, options.basis);
|
|
432
445
|
if (!identity) {
|
|
433
|
-
throw
|
|
446
|
+
throw markDependencyMissing(
|
|
447
|
+
new CredentialVerificationError(`issuer identity not found: ${payload.iss}`)
|
|
448
|
+
);
|
|
434
449
|
}
|
|
435
450
|
if (identity.isDeleted) {
|
|
436
451
|
throw new CredentialVerificationError(`issuer identity is deleted: ${payload.iss}`);
|
|
437
452
|
}
|
|
438
|
-
const publicKey = resolveKeyFromIdentity(identity, kid);
|
|
453
|
+
const publicKey = resolveKeyFromIdentity(identity, kid, identity.basisDeterminate === true);
|
|
439
454
|
try {
|
|
440
455
|
verifyJws({ token: jwsToken, publicKey });
|
|
441
456
|
} catch {
|
|
@@ -449,11 +464,8 @@ var verifyDFOSCredential = async (jwsToken, options) => {
|
|
|
449
464
|
if (decoded.header.cid !== credentialCID) {
|
|
450
465
|
throw new CredentialVerificationError("credential cid mismatch");
|
|
451
466
|
}
|
|
452
|
-
const
|
|
453
|
-
if (payload.
|
|
454
|
-
throw new CredentialVerificationError("credential not yet valid (iat is in the future)");
|
|
455
|
-
}
|
|
456
|
-
if (payload.exp <= now) {
|
|
467
|
+
const basisSeconds = options.basis !== void 0 ? basisUnixSeconds(options.basis) : options.nowUnix ?? Math.floor(Date.now() / 1e3);
|
|
468
|
+
if (payload.exp <= basisSeconds) {
|
|
457
469
|
throw new CredentialVerificationError("credential expired");
|
|
458
470
|
}
|
|
459
471
|
return {
|
|
@@ -469,6 +481,7 @@ var verifyDFOSCredential = async (jwsToken, options) => {
|
|
|
469
481
|
};
|
|
470
482
|
var verifyDelegationChain = async (credential, options) => {
|
|
471
483
|
const chain = [credential];
|
|
484
|
+
const revocationBasis = options.basis !== void 0 ? basisUnixSeconds(options.basis) : void 0;
|
|
472
485
|
let current = credential;
|
|
473
486
|
const maxDepth = 16;
|
|
474
487
|
for (let depth = 0; depth < maxDepth; depth++) {
|
|
@@ -487,10 +500,11 @@ var verifyDelegationChain = async (credential, options) => {
|
|
|
487
500
|
}
|
|
488
501
|
const parent = await verifyDFOSCredential(current.prf[0], {
|
|
489
502
|
resolveIdentity: options.resolveIdentity,
|
|
490
|
-
...options.
|
|
503
|
+
...options.basis !== void 0 ? { basis: options.basis } : {},
|
|
504
|
+
...options.nowUnix !== void 0 ? { nowUnix: options.nowUnix } : {}
|
|
491
505
|
});
|
|
492
506
|
if (options.isRevoked) {
|
|
493
|
-
const revoked = await options.isRevoked(parent.iss, parent.credentialCID,
|
|
507
|
+
const revoked = await options.isRevoked(parent.iss, parent.credentialCID, revocationBasis);
|
|
494
508
|
if (revoked) {
|
|
495
509
|
throw new CredentialVerificationError("parent credential in delegation chain is revoked");
|
|
496
510
|
}
|
|
@@ -594,6 +608,9 @@ var CredentialVerificationError = class extends Error {
|
|
|
594
608
|
};
|
|
595
609
|
|
|
596
610
|
export {
|
|
611
|
+
markDependencyMissing,
|
|
612
|
+
isDependencyMissing,
|
|
613
|
+
carryDependencyMissing,
|
|
597
614
|
MAX_CREDENTIAL_SIZE,
|
|
598
615
|
Attenuation,
|
|
599
616
|
DFOSCredentialPayload,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { b as Attenuation, e as CredentialVerificationError, D as DFOSCredentialPayload, i as MAX_CREDENTIAL_SIZE, R as RevocationChecker, V as VerifiedDFOSCredential,
|
|
1
|
+
export { b as Attenuation, e as CredentialVerificationError, D as DFOSCredentialPayload, i as MAX_CREDENTIAL_SIZE, R as ResolvedIdentity, r as RevocationChecker, V as VerifiedDFOSCredential, w as VerifiedDelegationChain, z as createDFOSCredential, B as decodeDFOSCredentialUnsafe, E as isAttenuated, F as matchesResource, H as verifyDFOSCredential, J as verifyDelegationChain } from '../dfos-credential-CtG0_n4P.js';
|
|
2
2
|
import 'zod';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -294,6 +294,11 @@ declare const VerifiedIdentity: z.ZodObject<{
|
|
|
294
294
|
publicKeyMultibase: z.ZodString;
|
|
295
295
|
}, z.core.$loose>>;
|
|
296
296
|
}, z.core.$strict>>;
|
|
297
|
+
seenKeys: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
298
|
+
id: z.ZodString;
|
|
299
|
+
type: z.ZodLiteral<"Multikey">;
|
|
300
|
+
publicKeyMultibase: z.ZodString;
|
|
301
|
+
}, z.core.$loose>>>;
|
|
297
302
|
}, z.core.$strict>;
|
|
298
303
|
type VerifiedIdentity = z.infer<typeof VerifiedIdentity>;
|
|
299
304
|
declare const ContentOperation: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
@@ -377,7 +382,7 @@ declare const MAX_CREDIT_CLAIM_SIZE = 4096;
|
|
|
377
382
|
* Credit claim: a claimant's signed assertion that it holds a named role on a
|
|
378
383
|
* content chain. A document-plane artifact — it is NOT gossiped and relays are not
|
|
379
384
|
* credit-claim aware; a claim travels inside the document bytes that embed it (see
|
|
380
|
-
* `specs/
|
|
385
|
+
* `specs/CONTENT-MODEL.md`).
|
|
381
386
|
*
|
|
382
387
|
* `contentId` is the binder — the STABLE 31-char content chain id, never a
|
|
383
388
|
* documentCID or a chain head CID. Binding to the chain (not a document) is what
|
|
@@ -500,6 +505,24 @@ interface VerifiedDFOSCredential {
|
|
|
500
505
|
/** kid from the JWS header */
|
|
501
506
|
signerKeyId: string;
|
|
502
507
|
}
|
|
508
|
+
/**
|
|
509
|
+
* The identity a resolver answers with, plus the one fact about that answer only
|
|
510
|
+
* the resolver knows: whether it is FINAL for the basis it was asked about.
|
|
511
|
+
*
|
|
512
|
+
* A resolver whose copy of the chain runs past the basis answers determinately,
|
|
513
|
+
* and a key missing from that state is a verdict. A resolver answering from a
|
|
514
|
+
* copy that ends at or before the basis does not: an operation dated at or
|
|
515
|
+
* before the basis can still arrive and add the key, so the miss is a dependency
|
|
516
|
+
* miss the caller retries. Absent is the retryable reading, which is the safe
|
|
517
|
+
* default: only a resolver that can say its answer is final gets a verdict.
|
|
518
|
+
*/
|
|
519
|
+
type ResolvedIdentity = VerifiedIdentity & {
|
|
520
|
+
/**
|
|
521
|
+
* True when no operation dated at or before the basis can still arrive and
|
|
522
|
+
* change this key state.
|
|
523
|
+
*/
|
|
524
|
+
basisDeterminate?: boolean;
|
|
525
|
+
};
|
|
503
526
|
interface VerifiedDelegationChain {
|
|
504
527
|
/** The leaf credential */
|
|
505
528
|
credential: VerifiedDFOSCredential;
|
|
@@ -512,8 +535,8 @@ interface VerifiedDelegationChain {
|
|
|
512
535
|
* Check whether a credential (leaf or any parent) has been revoked.
|
|
513
536
|
*
|
|
514
537
|
* `asOfUnix` selects WHICH question is being asked, and the two are different
|
|
515
|
-
* decisions: **acceptance is a freshness decision; verification
|
|
516
|
-
*
|
|
538
|
+
* decisions: **acceptance is a freshness decision; verification at a basis is a
|
|
539
|
+
* validity decision.**
|
|
517
540
|
*
|
|
518
541
|
* - **Omitted, or `<= 0` (timeless)** — "is this credential revoked as far as you
|
|
519
542
|
* know right now?". The freshness question. Used by acceptance gates: relay
|
|
@@ -524,10 +547,10 @@ interface VerifiedDelegationChain {
|
|
|
524
547
|
* 1970) therefore gets the stricter answer in both languages.
|
|
525
548
|
* - **Positive (as-of)** — "was this credential already revoked at `asOfUnix`?".
|
|
526
549
|
* The validity question. Return true only if a revocation exists AND its
|
|
527
|
-
* signed `createdAt` is ≤ `asOfUnix`. Used when verifying
|
|
528
|
-
*
|
|
529
|
-
*
|
|
530
|
-
*
|
|
550
|
+
* signed `createdAt` is ≤ `asOfUnix`. Used when verifying an artifact against
|
|
551
|
+
* a basis, where `asOfUnix` is the basis in integer Unix seconds. A revocation
|
|
552
|
+
* signed AFTER the basis does not invalidate the artifact — see CREDENTIALS.md
|
|
553
|
+
* "Revocation against the basis".
|
|
531
554
|
*
|
|
532
555
|
* An implementation that ignores `asOfUnix` degrades to the timeless answer,
|
|
533
556
|
* which is always the stricter (safe) direction — it can only reject history
|
|
@@ -559,13 +582,35 @@ declare const createDFOSCredential: (options: {
|
|
|
559
582
|
/**
|
|
560
583
|
* Verify a DFOS credential — signature, schema, expiry, CID integrity
|
|
561
584
|
*
|
|
585
|
+
* Every check runs against ONE basis time (PROTOCOL, Time basis): the issuer's
|
|
586
|
+
* key must be effective in the identity's state as of the basis, and `exp` must
|
|
587
|
+
* be strictly greater than the basis in integer Unix seconds. `iat` is
|
|
588
|
+
* informational and gates nothing.
|
|
589
|
+
*
|
|
562
590
|
* Does NOT verify the delegation chain. Use `verifyDelegationChain` for full
|
|
563
591
|
* chain verification including attenuation enforcement.
|
|
564
592
|
*/
|
|
565
593
|
declare const verifyDFOSCredential: (jwsToken: string, options: {
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
594
|
+
/**
|
|
595
|
+
* Resolve a DID to its verified identity state AS OF `basis`. Called with
|
|
596
|
+
* the basis this verification runs at, or with none when the presentation is
|
|
597
|
+
* ephemeral and the answer is head state. A resolver that knows its answer
|
|
598
|
+
* is final for the basis says so on the result (`ResolvedIdentity`).
|
|
599
|
+
*/
|
|
600
|
+
resolveIdentity: (did: string, basis?: string) => Promise<ResolvedIdentity | undefined>;
|
|
601
|
+
/**
|
|
602
|
+
* The basis time, in the `createdAt` grammar — the operation's own
|
|
603
|
+
* `createdAt` for a credential carried inline in a committed operation.
|
|
604
|
+
* Omitted for an ephemeral presentation, where the basis is now.
|
|
605
|
+
*/
|
|
606
|
+
basis?: string;
|
|
607
|
+
/**
|
|
608
|
+
* The ephemeral clock in integer Unix seconds, for a caller that carries its
|
|
609
|
+
* own. It reaches `exp` and nothing else: key resolution on the ephemeral
|
|
610
|
+
* path is head state, which is what "the basis is now" means. Ignored when
|
|
611
|
+
* `basis` is present, and defaults to `floor(Date.now() / 1000)`.
|
|
612
|
+
*/
|
|
613
|
+
nowUnix?: number;
|
|
569
614
|
}) => Promise<VerifiedDFOSCredential>;
|
|
570
615
|
/**
|
|
571
616
|
* Verify a full delegation chain — walk `prf`, confirm monotonic attenuation,
|
|
@@ -578,23 +623,29 @@ declare const verifyDFOSCredential: (jwsToken: string, options: {
|
|
|
578
623
|
*
|
|
579
624
|
* The chain terminates when a credential has `prf: []` (root credential). The
|
|
580
625
|
* root credential's `iss` must equal `rootDID`.
|
|
626
|
+
*
|
|
627
|
+
* ONE BASIS FOR THE WHOLE WALK. Every hop — each parent's signing key, each
|
|
628
|
+
* `exp`, each revocation — resolves against the same basis the leaf did, so a
|
|
629
|
+
* chain either held at that instant or it did not.
|
|
581
630
|
*/
|
|
582
631
|
declare const verifyDelegationChain: (credential: VerifiedDFOSCredential, options: {
|
|
583
|
-
|
|
632
|
+
/** Resolve a DID to its verified identity state as of `basis`. */
|
|
633
|
+
resolveIdentity: (did: string, basis?: string) => Promise<ResolvedIdentity | undefined>;
|
|
584
634
|
/** The expected root authority DID (e.g., content chain creator) */
|
|
585
635
|
rootDID: string;
|
|
586
|
-
/** Current time in seconds (defaults to Date.now() / 1000) */
|
|
587
|
-
now?: number;
|
|
588
636
|
/** Check if a credential has been revoked (checked at every level of the chain) */
|
|
589
637
|
isRevoked?: RevocationChecker;
|
|
590
638
|
/**
|
|
591
|
-
*
|
|
592
|
-
* `
|
|
593
|
-
*
|
|
594
|
-
|
|
595
|
-
|
|
639
|
+
* The basis time, in the `createdAt` grammar. Omitted for an ephemeral
|
|
640
|
+
* presentation: `exp` runs against the wall clock and revocation is asked
|
|
641
|
+
* timelessly, which is the stricter direction (see `RevocationChecker`).
|
|
642
|
+
*/
|
|
643
|
+
basis?: string;
|
|
644
|
+
/**
|
|
645
|
+
* The ephemeral clock in integer Unix seconds, threaded to every hop's `exp`
|
|
646
|
+
* and nothing else. Ignored when `basis` is present.
|
|
596
647
|
*/
|
|
597
|
-
|
|
648
|
+
nowUnix?: number;
|
|
598
649
|
}) => Promise<VerifiedDelegationChain>;
|
|
599
650
|
/**
|
|
600
651
|
* Check if `childAtt` is a valid attenuation of `parentAtt`
|
|
@@ -639,4 +690,4 @@ declare class CredentialVerificationError extends Error {
|
|
|
639
690
|
constructor(message: string);
|
|
640
691
|
}
|
|
641
692
|
|
|
642
|
-
export { ARTIFACT_CID_ANCHOR_RE as A,
|
|
693
|
+
export { ARTIFACT_CID_ANCHOR_RE as A, decodeDFOSCredentialUnsafe as B, CONTENT_ID_ANCHOR_RE as C, DFOSCredentialPayload as D, isAttenuated as E, matchesResource as F, parseProtocolTimestampUnix as G, verifyDFOSCredential as H, IdentityOperation as I, verifyDelegationChain as J, MAX_ARTIFACT_PAYLOAD_SIZE as M, type ResolvedIdentity as R, ServiceEntry as S, type VerifiedDFOSCredential as V, ArtifactPayload as a, Attenuation as b, ContentOperation as c, CountersignPayload as d, CredentialVerificationError as e, CreditClaimPayload as f, DeclaredKeyState as g, Iso8601 as h, MAX_CREDENTIAL_SIZE as i, MAX_CREDIT_CLAIM_SIZE as j, MAX_KEY_PROOFS as k, MAX_OPERATION_SIZE as l, MAX_SERVICES_ENTRIES as m, MAX_SERVICES_PAYLOAD_SIZE as n, MAX_SIGN_REQUEST_PAYLOAD_SIZE as o, MAX_SIGN_REQUEST_SIZE as p, MultikeyPublicKey as q, type RevocationChecker as r, RevocationPayload as s, ServicesArray as t, SignRequestPayload as u, type Signer as v, type VerifiedDelegationChain as w, VerifiedIdentity as x, VoidKeyMembership as y, createDFOSCredential as z };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { JwsHeader, JwsVerificationError, JwtClaims, JwtCreateOptions, JwtHeader, JwtVerificationError, JwtVerifyOptions, PrefixedID, assertJwsProfile, base64urlDecode, base64urlEncode, createJws, createJwt, createNewEd25519Keypair, dagCborCanonicalEncode, decodeJwsUnsafe, decodeJwtUnsafe, generateId, generateIdNoPrefix, importEd25519Keypair, isCanonicallyEqual, isValidEd25519Signature, isValidId, normalizedId, parseDagCborCID, sha256, signPayloadEd25519, verifyJws, verifyJwt } from './crypto/index.js';
|
|
2
|
-
export { A as ARTIFACT_CID_ANCHOR_RE, a as ArtifactPayload, b as Attenuation, C as CONTENT_ID_ANCHOR_RE, c as ContentOperation, d as CountersignPayload, e as CredentialVerificationError, f as CreditClaimPayload, D as DFOSCredentialPayload, g as DeclaredKeyState, I as IdentityOperation, h as Iso8601, M as MAX_ARTIFACT_PAYLOAD_SIZE, i as MAX_CREDENTIAL_SIZE, j as MAX_CREDIT_CLAIM_SIZE, k as MAX_KEY_PROOFS, l as MAX_OPERATION_SIZE, m as MAX_SERVICES_ENTRIES, n as MAX_SERVICES_PAYLOAD_SIZE, o as MAX_SIGN_REQUEST_PAYLOAD_SIZE, p as MAX_SIGN_REQUEST_SIZE, q as MultikeyPublicKey, R as
|
|
2
|
+
export { A as ARTIFACT_CID_ANCHOR_RE, a as ArtifactPayload, b as Attenuation, C as CONTENT_ID_ANCHOR_RE, c as ContentOperation, d as CountersignPayload, e as CredentialVerificationError, f as CreditClaimPayload, D as DFOSCredentialPayload, g as DeclaredKeyState, I as IdentityOperation, h as Iso8601, M as MAX_ARTIFACT_PAYLOAD_SIZE, i as MAX_CREDENTIAL_SIZE, j as MAX_CREDIT_CLAIM_SIZE, k as MAX_KEY_PROOFS, l as MAX_OPERATION_SIZE, m as MAX_SERVICES_ENTRIES, n as MAX_SERVICES_PAYLOAD_SIZE, o as MAX_SIGN_REQUEST_PAYLOAD_SIZE, p as MAX_SIGN_REQUEST_SIZE, q as MultikeyPublicKey, R as ResolvedIdentity, r as RevocationChecker, s as RevocationPayload, S as ServiceEntry, t as ServicesArray, u as SignRequestPayload, v as Signer, V as VerifiedDFOSCredential, w as VerifiedDelegationChain, x as VerifiedIdentity, y as VoidKeyMembership, z as createDFOSCredential, B as decodeDFOSCredentialUnsafe, E as isAttenuated, F as matchesResource, G as parseProtocolTimestampUnix, H as verifyDFOSCredential, J as verifyDelegationChain } from './dfos-credential-CtG0_n4P.js';
|
|
3
3
|
export { AnchorKind, CreditClaimFailureReason, CreditClaimVerifyError, CreditEntry, CreditEntryState, ED25519_PRIV_MULTICODEC, ED25519_PUB_MULTICODEC, RECOGNIZED_SERVICE_TYPES, SignRequestFailureReason, SignRequestVerifyError, VerifiedArtifact, VerifiedContentChain, VerifiedCountersignature, VerifiedCreditClaim, VerifiedCreditEntry, VerifiedRevocation, VerifiedSignRequest, anchorsByLabel, assertCanonicalSignRequestPayload, assertServicesWithinCap, buildSignRequest, classifyAnchor, decodeMultikey, deriveChainIdentifier, deriveContentId, encodeEd25519Multikey, isRecognizedServiceType, relayEndpoints, signArtifact, signContentOperation, signCountersignature, signCreditClaim, signIdentityOperation, signRevocation, verifyArtifact, verifyContentChain, verifyContentExtensionFromTrustedState, verifyCountersignature, verifyCreditClaim, verifyCreditEntry, verifyIdentityChain, verifyIdentityExtensionFromTrustedState, verifyRevocation, verifySignRequest } from './chain/index.js';
|
|
4
4
|
export { ApiRequestVerifyError, DEFAULT_PROOF_SKEW_SECONDS, DEFAULT_PROOF_WINDOW_SECONDS, DFOS_AUTH_SCHEME, EMPTY_BODY_SHA256, IDENTITY_PROOF_JWS_TYP, IdentityProofPayload, MAX_BODY_BYTES, MAX_PROOF_FRESHNESS_SPAN_SECONDS, MAX_REQUEST_PROOF_SIZE, ParsedProofPayload, ProofEnvelopeInput, ProofExtraMembers, ProofPresenterState, REQUEST_PROOF_JWS_TYP, RequestProofFailurePhase, RequestProofFailureReason, RequestProofPayload, ResolveProofPresenter, SignApiIdentityRequestInput, SignApiRequestInput, VerifiedProofEnvelope, apiIdentitySigningInput, apiRequestSigningInput, assertProofVerifierConfig, buildApiAuthHeaders, buildApiIdentityHeaders, canonicalExtraMembers, invalidProof, misconfiguredProof, parseDfosAuthorization, sha256BodyHash, signApiIdentityRequest, signApiRequest, unverifiableProof, verifyIdentityProofEnvelope, verifyRequestProofEnvelope } from './credentials/index.js';
|
|
5
5
|
export { DEFAULT_KEY_PROOF_SKEW_SECONDS, KEY_ADD_JWS_TYP, KEY_ROLES, KeyProofFailureReason, KeyProofPayload, KeyProofVerifyError, KeyRole, MAX_KEY_PROOF_SIZE, SignKeyProofInput, VerifiedKeyProof, VerifyChainKeyProofOptions, VerifyKeyProofOptions, isCanonicalRoleSet, keyProofSigningInput, keyWordFingerprint, parseRoleSet, roleSetCovers, serializeRoleSet, signKeyProof, unsafeKeyProofSubject, verifyChainKeyProof, verifyKeyProof } from './key-proof/index.js';
|
|
@@ -7,3 +7,24 @@ export { FoldOperation, INDEX_V1_SCHEMA, IndexDelta, IndexDocument, IndexEntry,
|
|
|
7
7
|
import 'multiformats';
|
|
8
8
|
import 'multiformats/cid';
|
|
9
9
|
import 'zod';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Mark an error as "a referenced identity or key is not resolvable HERE".
|
|
13
|
+
*
|
|
14
|
+
* Returns the same error so it can be marked inline at a throw site:
|
|
15
|
+
* `throw markDependencyMissing(new CredentialVerificationError(...))`.
|
|
16
|
+
*/
|
|
17
|
+
declare const markDependencyMissing: <E extends Error>(err: E) => E;
|
|
18
|
+
/** True if `err` carries the marker. */
|
|
19
|
+
declare const isDependencyMissing: (err: unknown) => boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Carry the marker from a caught error onto the error that replaces it.
|
|
22
|
+
*
|
|
23
|
+
* This is the TypeScript spelling of Go's `%w`: a layer that re-throws with more
|
|
24
|
+
* context must not eat the one fact only the inner layer knew. Every re-wrap
|
|
25
|
+
* between a resolver and a relay's classifier goes through this, or the marker
|
|
26
|
+
* is lost at that boundary and the classification silently reverts to a guess.
|
|
27
|
+
*/
|
|
28
|
+
declare const carryDependencyMissing: <E extends Error>(cause: unknown, wrapped: E) => E;
|
|
29
|
+
|
|
30
|
+
export { carryDependencyMissing, isDependencyMissing, markDependencyMissing };
|
package/dist/index.js
CHANGED
|
@@ -52,7 +52,7 @@ import {
|
|
|
52
52
|
verifyIdentityExtensionFromTrustedState,
|
|
53
53
|
verifyRevocation,
|
|
54
54
|
verifySignRequest
|
|
55
|
-
} from "./chunk-
|
|
55
|
+
} from "./chunk-LMAPBTDW.js";
|
|
56
56
|
import {
|
|
57
57
|
ApiRequestVerifyError,
|
|
58
58
|
Attenuation,
|
|
@@ -74,10 +74,13 @@ import {
|
|
|
74
74
|
buildApiAuthHeaders,
|
|
75
75
|
buildApiIdentityHeaders,
|
|
76
76
|
canonicalExtraMembers,
|
|
77
|
+
carryDependencyMissing,
|
|
77
78
|
createDFOSCredential,
|
|
78
79
|
decodeDFOSCredentialUnsafe,
|
|
79
80
|
invalidProof,
|
|
80
81
|
isAttenuated,
|
|
82
|
+
isDependencyMissing,
|
|
83
|
+
markDependencyMissing,
|
|
81
84
|
matchesResource,
|
|
82
85
|
misconfiguredProof,
|
|
83
86
|
parseDfosAuthorization,
|
|
@@ -89,7 +92,7 @@ import {
|
|
|
89
92
|
verifyDelegationChain,
|
|
90
93
|
verifyIdentityProofEnvelope,
|
|
91
94
|
verifyRequestProofEnvelope
|
|
92
|
-
} from "./chunk-
|
|
95
|
+
} from "./chunk-T2YR5KWQ.js";
|
|
93
96
|
import {
|
|
94
97
|
DEFAULT_KEY_PROOF_SKEW_SECONDS,
|
|
95
98
|
KEY_ADD_JWS_TYP,
|
|
@@ -213,6 +216,7 @@ export {
|
|
|
213
216
|
buildSignRequest,
|
|
214
217
|
byteCompare,
|
|
215
218
|
canonicalExtraMembers,
|
|
219
|
+
carryDependencyMissing,
|
|
216
220
|
classifyAnchor,
|
|
217
221
|
compareHeadPreference,
|
|
218
222
|
compareLinear,
|
|
@@ -237,12 +241,14 @@ export {
|
|
|
237
241
|
isAttenuated,
|
|
238
242
|
isCanonicalRoleSet,
|
|
239
243
|
isCanonicallyEqual,
|
|
244
|
+
isDependencyMissing,
|
|
240
245
|
isRecognizedServiceType,
|
|
241
246
|
isValidEd25519Signature,
|
|
242
247
|
isValidId,
|
|
243
248
|
keyProofSigningInput,
|
|
244
249
|
keyWordFingerprint,
|
|
245
250
|
linearize,
|
|
251
|
+
markDependencyMissing,
|
|
246
252
|
matchesResource,
|
|
247
253
|
misconfiguredProof,
|
|
248
254
|
normalizedId,
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://schemas.dfos.com/credit-claim/v1",
|
|
4
4
|
"title": "Credit Claim",
|
|
5
|
-
"description": "The payload of a did:dfos:credit-claim JWS — a claimant's own signed assertion that it holds a named role on a content chain. Unlike post/v1, profile/v1, and index/v1, this is NOT a document committed to a chain by CID: it is an envelope payload carried inside document bytes, in the claim field of a credits entry (see $defs/creditEntry). Attribution, not authorization — a credit claim grants nothing. Unknown top-level fields are deliberately NOT rejected here (no additionalProperties: false, unlike the document schemas): this is a wire payload under the protocol's MUST-ignore-unknown rule, and the CID commits to the exact bytes, so a verifier that stripped unknown keys would fail its own CID check. Full semantics, the two-way bind, and the four verification states: https://protocol.dfos.com/credits",
|
|
5
|
+
"description": "The payload of a did:dfos:credit-claim JWS — a claimant's own signed assertion that it holds a named role on a content chain. Unlike post/v1, profile/v1, and index/v1, this is NOT a document committed to a chain by CID: it is an envelope payload carried inside document bytes, in the claim field of a credits entry (see $defs/creditEntry). Attribution, not authorization — a credit claim grants nothing. Unknown top-level fields are deliberately NOT rejected here (no additionalProperties: false, unlike the document schemas): this is a wire payload under the protocol's MUST-ignore-unknown rule, and the CID commits to the exact bytes, so a verifier that stripped unknown keys would fail its own CID check. Full semantics, the two-way bind, and the four verification states: https://protocol.dfos.com/content-model#credits",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"required": ["version", "type", "contentId", "did", "role", "createdAt"],
|
|
8
8
|
"properties": {
|
package/schemas/post.v1.json
CHANGED
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"items": {
|
|
44
44
|
"$ref": "#/$defs/credit"
|
|
45
45
|
},
|
|
46
|
-
"description": "Ordered authorship credits. Array order is display order; the first entry is the primary author. Omit entirely for unattributed content. A bare entry is the assertion tier of the authorship lattice — the operation signer asserts it; a credited DID upgrades its credit to proof by adding a credit claim in the entry's claim field (see https://protocol.dfos.com/credits)."
|
|
46
|
+
"description": "Ordered authorship credits. Array order is display order; the first entry is the primary author. Omit entirely for unattributed content. A bare entry is the assertion tier of the authorship lattice — the operation signer asserts it; a credited DID upgrades its credit to proof by adding a credit claim in the entry's claim field (see https://protocol.dfos.com/content-model#credits)."
|
|
47
47
|
}
|
|
48
48
|
},
|
|
49
49
|
"additionalProperties": false,
|