@kubun/credential 0.17.4 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/lib/api.d.ts +61 -30
  2. package/lib/api.js +455 -111
  3. package/lib/authority.d.ts +4 -0
  4. package/lib/authority.js +3 -0
  5. package/lib/branch.d.ts +4 -0
  6. package/lib/branch.js +24 -0
  7. package/lib/builder.d.ts +5 -0
  8. package/lib/builder.js +8 -6
  9. package/lib/canonical-token.d.ts +2 -0
  10. package/lib/canonical-token.js +10 -0
  11. package/lib/errors.d.ts +39 -1
  12. package/lib/errors.js +77 -0
  13. package/lib/fork-repair.d.ts +19 -0
  14. package/lib/fork-repair.js +3 -0
  15. package/lib/grantor-authority.d.ts +26 -0
  16. package/lib/grantor-authority.js +43 -0
  17. package/lib/index.d.ts +15 -6
  18. package/lib/index.js +7 -2
  19. package/lib/key-cache.d.ts +4 -3
  20. package/lib/key-cache.js +22 -15
  21. package/lib/manager-context.d.ts +50 -0
  22. package/lib/manager-context.js +82 -0
  23. package/lib/manager-entries.d.ts +3 -0
  24. package/lib/manager-entries.js +585 -0
  25. package/lib/manager-keys.d.ts +3 -0
  26. package/lib/manager-keys.js +413 -0
  27. package/lib/manager-open.d.ts +45 -0
  28. package/lib/manager-open.js +421 -0
  29. package/lib/manager-records.d.ts +94 -0
  30. package/lib/manager-records.js +291 -0
  31. package/lib/manager-removal-recovery.d.ts +7 -0
  32. package/lib/manager-removal-recovery.js +211 -0
  33. package/lib/manager-rotate.d.ts +15 -0
  34. package/lib/manager-rotate.js +536 -0
  35. package/lib/manager-types.d.ts +337 -0
  36. package/lib/manager-types.js +1 -0
  37. package/lib/manager.d.ts +2 -293
  38. package/lib/manager.js +38 -1073
  39. package/lib/manifest-token.d.ts +31 -0
  40. package/lib/manifest-token.js +51 -0
  41. package/lib/provider.d.ts +15 -2
  42. package/lib/provider.js +39 -0
  43. package/lib/record-types.d.ts +65 -0
  44. package/lib/record-types.js +865 -0
  45. package/lib/refreshing-credential-provider.d.ts +6 -2
  46. package/lib/refreshing-credential-provider.js +76 -13
  47. package/lib/suite.d.ts +5 -4
  48. package/lib/suite.js +2 -2
  49. package/package.json +16 -15
  50. package/lib/op.d.ts +0 -128
  51. package/lib/op.js +0 -304
@@ -0,0 +1,337 @@
1
+ import type { MethodRegistry, SigningIdentity } from '@kokuin/token';
2
+ import type { HLC } from '@kubun/hlc';
3
+ import type { CredentialStoreAPI } from '@kubun/store-credential';
4
+ import type { Runtime } from '@sozai/runtime';
5
+ import type { CredentialAuthority } from './authority.js';
6
+ import type { ForkRepairResult } from './fork-repair.js';
7
+ import type { PinThrottleParams } from './pin-throttle.js';
8
+ import type { CredentialEntryProvenance } from './provenance.js';
9
+ import type { FactorInput, FactorInputs, SecretFactorKind } from './suite.js';
10
+ export type CreateCredentialManagerParams = {
11
+ store: CredentialStoreAPI;
12
+ /**
13
+ * This device's identity. NOT required to be a `KeyAgreementIdentity`: a device
14
+ * that cannot decrypt can still read metadata, and will be able to unwrap by
15
+ * passphrase once that factor exists. What it cannot do is take the `did`
16
+ * default in {@link CredentialManager.createKey}.
17
+ *
18
+ * It MUST be able to sign, which a reading identity need not: every row this
19
+ * manager writes carries a signed op, and a device that could write rows no
20
+ * peer can authenticate would be writing into a lane that has to drop them.
21
+ */
22
+ identity: SigningIdentity;
23
+ /**
24
+ * Host runtime, propagated rather than constructed here. This package runs on
25
+ * React Native and in browsers as well as on node, and content keys, IVs and
26
+ * factor secrets are the values that most need the platform's own CSPRNG
27
+ * rather than whatever `crypto` a bundler resolved.
28
+ */
29
+ runtime: Runtime;
30
+ /**
31
+ * The device's one clock, passed in rather than built here.
32
+ *
33
+ * Required, and deliberately not defaulted: a connector builds one manager per
34
+ * transaction, so a manager that constructed its own would start from
35
+ * `lastWallTime = 0` every time and two of them writing in the same
36
+ * millisecond would emit an identical `(wallTime, counter, nodeID)` — a
37
+ * colliding merkle leaf and an order tie with no tiebreak.
38
+ */
39
+ hlc: HLC;
40
+ /**
41
+ * Who this identity is relative to a key. Defaults to the device answer:
42
+ * it administers what it owns and nothing else.
43
+ *
44
+ * The same port the plugin's query context takes, deliberately — a device
45
+ * authority refuses a grant recipient gating someone else's key, and a
46
+ * delegation authority will let an agent gate its controller's, without
47
+ * either layer growing a predicate of its own.
48
+ */
49
+ authority?: CredentialAuthority;
50
+ /** Controller-owned writes require a recovery check; an absent check fails closed. */
51
+ removalRecovery?: (RecoverRemovedDevicesParams & {
52
+ headVersion(ownerDID: string): Promise<string>;
53
+ }) | {
54
+ ensure(): Promise<void>;
55
+ removedDevices?(ownerDID: string): Promise<ReadonlySet<string>>;
56
+ };
57
+ /**
58
+ * How long an unlocked key stays in memory. Eviction is lazy — checked when
59
+ * the cache is read — because a timer would keep the host process alive and
60
+ * make every test wait on a clock it does not control.
61
+ */
62
+ unlockTTL?: number;
63
+ /**
64
+ * Failed-PIN backoff. Its clock comes from `now`, so a caller cannot give the
65
+ * throttle a different notion of time from the cache's.
66
+ */
67
+ pinThrottle?: Omit<PinThrottleParams, 'now'>;
68
+ /** Injectable so a TTL or backoff test does not sleep. */
69
+ now?: () => number;
70
+ };
71
+ /** The device that may author a live OAuth grant and the grant's local name. */
72
+ export type GrantEntryParams = {
73
+ grantDeviceDID: string;
74
+ grantName: string;
75
+ grantOwnerDID?: string;
76
+ };
77
+ export type CreateKeyParams = {
78
+ suite?: number;
79
+ /** One wrapping, whose factors are an AND. Shorthand for a single-entry `wrappings`. */
80
+ factors?: Array<FactorInput>;
81
+ /**
82
+ * Several wrappings — an OR of ANDs, one per way of opening the key.
83
+ *
84
+ * A key addressed to two recipients is not two keys and is not a grant: both
85
+ * wrappings exist from the first write, which is what a connector needs when
86
+ * the account owner and the server that runs sync are different DIDs.
87
+ */
88
+ wrappings?: Array<Array<FactorInput>>;
89
+ /**
90
+ * Who may later grant, rotate and revoke. Defaults to this device, which is
91
+ * the peer case. A server creating a key on a viewer's behalf passes the
92
+ * viewer, and stays a recipient rather than the owner.
93
+ */
94
+ ownerDID?: string;
95
+ /**
96
+ * Resolvers for recipient DIDs that carry no key material of their own
97
+ * (`did:kokuin:`). Passed per call rather than held on the manager, so a
98
+ * request-scoped resolver never outlives its request in the cached manager.
99
+ */
100
+ methods?: MethodRegistry;
101
+ };
102
+ export type RotateKeyParams = {
103
+ /**
104
+ * Migrate to a different ciphersuite. Rotation is the only path between
105
+ * suites: it already re-encrypts every entry and re-makes every wrapping,
106
+ * which is exactly what a suite change needs.
107
+ */
108
+ suite?: number;
109
+ /**
110
+ * Secrets for the surviving factors, keyed by kind — the same prompt
111
+ * `addFactor` needs, for the same reason: a descriptor holds a salt and a
112
+ * cost, never the secret, so a wrapping carrying one cannot be re-made at the
113
+ * new version without it.
114
+ *
115
+ * Only the rotating identity's own wrappings can carry a secret factor, since
116
+ * `addFactor` refuses one on a key it does not administer — so the prompt is
117
+ * always one the caller is present for.
118
+ */
119
+ inputs?: FactorInputs;
120
+ /** Resolvers for recipient DIDs that need one (`did:kokuin:`), for re-made `did` wrappings. */
121
+ methods?: MethodRegistry;
122
+ /**
123
+ * Overrides the constructed authority for this one call, so a provider-cached
124
+ * manager holds no request state. No production caller feeds one yet — the
125
+ * group-grant wiring that will is a follow-up.
126
+ */
127
+ authority?: CredentialAuthority;
128
+ };
129
+ /** Optionals shared by the factor-editing verbs `addFactor` and `removeFactor`. */
130
+ export type FactorChangeParams = {
131
+ /**
132
+ * Secrets for the factors that SURVIVE the change, keyed by kind — a
133
+ * descriptor holds a salt, never a secret, so the surviving wrapping's KEK
134
+ * cannot be re-derived without them.
135
+ */
136
+ inputs?: FactorInputs;
137
+ /** Resolvers for recipient DIDs that need one (`did:kokuin:`), for re-made `did` wrappings. */
138
+ methods?: MethodRegistry;
139
+ /** Per-call authority overriding the constructed default. See `RotateKeyParams.authority`. */
140
+ authority?: CredentialAuthority;
141
+ };
142
+ /** See `grant`. */
143
+ export type GrantKeyParams = {
144
+ /** Resolvers for recipient DIDs that carry no key material of their own (`did:kokuin:`). */
145
+ methods?: MethodRegistry;
146
+ /** Per-call authority overriding the constructed default. See `RotateKeyParams.authority`. */
147
+ authority?: CredentialAuthority;
148
+ };
149
+ /** See `reKeyEntry`. */
150
+ export type RecoverRemovedDevicesParams = {
151
+ /** Current-head membership resolver; a removed device resolves to null. */
152
+ resolveController(did: string): Promise<string | null>;
153
+ /** Current controller denial set, including recipients that authored no record. */
154
+ removedDevices?(ownerDID: string): Promise<ReadonlySet<string>>;
155
+ methods?: MethodRegistry;
156
+ };
157
+ export type ReKeyEntryParams = {
158
+ /** Per-call authority overriding the constructed default. See `RotateKeyParams.authority`. */
159
+ authority?: CredentialAuthority;
160
+ };
161
+ export type CredentialEntryAddress = {
162
+ entryID: string;
163
+ ownerDID: string;
164
+ };
165
+ export type UpdateCredentialEntryIfUnchangedParams = CredentialEntryAddress & {
166
+ plaintext: Uint8Array;
167
+ expectedHLC: string;
168
+ };
169
+ export type ReKeyCredentialEntryParams = CredentialEntryAddress & ReKeyEntryParams & {
170
+ toKeyID: string;
171
+ };
172
+ /**
173
+ * Why a key cannot be opened here, as opposed to not being open yet.
174
+ *
175
+ * Carried so that `status` can answer rather than throw: the one call whose job
176
+ * is to report a key as unreadable must not fail on the key that is unreadable.
177
+ */
178
+ export type CredentialUnopenableReason =
179
+ /** A newer peer's ciphersuite. A different build could open it; this one cannot. */
180
+ 'unsupported-suite'
181
+ /** No wrapping at the key's current version — a rotation that did not finish. */
182
+ | 'no-wrapping'
183
+ /** Every wrapping belongs to another recipient. Another device can open it. */
184
+ | 'not-addressed' | 'revoked';
185
+ /**
186
+ * Derived, never stored. A persisted `unlocked` column would be a belief that
187
+ * survives a restart it should not.
188
+ *
189
+ * `available` is separated from `unlocked` because they differ in what the
190
+ * caller must do, which is nothing, and in what the machine must do, which is
191
+ * one unwrap. A `did`-only key is `available` until first read, and a TTL
192
+ * eviction returns it there rather than to `locked` — no prompt ever becomes
193
+ * necessary.
194
+ */
195
+ export type CredentialKeyStatus = {
196
+ state: 'unlocked';
197
+ } | {
198
+ state: 'available';
199
+ } | {
200
+ state: 'locked';
201
+ /**
202
+ * One entry per wrapping that could be opened here: an OR of ANDs. Nested
203
+ * because flattening loses the difference between a key openable by a pin
204
+ * OR a passphrase and one demanding both, and those are different prompts.
205
+ * `did` factors are absent — this device supplies them without asking.
206
+ */
207
+ unlockOptions: Array<Array<SecretFactorKind>>;
208
+ } | {
209
+ state: 'unopenable';
210
+ reason: CredentialUnopenableReason;
211
+ };
212
+ export type CredentialGrantResult = {
213
+ /** The key version the recipient's wrapping sits at. */
214
+ version: number;
215
+ /**
216
+ * False when the recipient already held a wrapping and nothing was written.
217
+ * A caller broadcasting the grant emits its frame either way — that is what
218
+ * makes a repeat grant the retry path for a frame that never arrived.
219
+ */
220
+ created: boolean;
221
+ };
222
+ export type CredentialManager = {
223
+ /** Store instance this manager reads and writes through. */
224
+ readonly store: CredentialStoreAPI;
225
+ /** Factor kinds this device can supply. Empty means it can hold no credentials. */
226
+ availableFactors(): Array<string>;
227
+ /**
228
+ * Open a key and hold its content key in memory.
229
+ *
230
+ * A `did`-only key needs no call: it opens on demand. This is for the ones
231
+ * carrying a factor only the user can supply.
232
+ */
233
+ unlock(keyID: string, inputs?: FactorInputs): Promise<void>;
234
+ /** Zero the cached content key. TTL eviction does the same thing. */
235
+ lock(keyID: string): void;
236
+ lockAll(): void;
237
+ status(keyID: string, version?: number): Promise<CredentialKeyStatus>;
238
+ /**
239
+ * Add a factor to THIS device's wrapping, leaving every other recipient's
240
+ * alone. Adding a `pin` therefore gates this device and no other, which is the
241
+ * intended granularity.
242
+ *
243
+ * `params.inputs` must carry the secret for every factor that SURVIVES the
244
+ * change, not only the one being added: the new wrapping's key is derived from
245
+ * all of them, and a descriptor holds a salt, never a secret.
246
+ */
247
+ addFactor(keyID: string, factor: FactorInput, params?: FactorChangeParams): Promise<void>;
248
+ /** Remove a factor kind from this device's wrapping. See `addFactor` on `params.inputs`. */
249
+ removeFactor(keyID: string, kind: string, params?: FactorChangeParams): Promise<void>;
250
+ createKey(params?: CreateKeyParams): Promise<string>;
251
+ /**
252
+ * Retire this key's content key: mint a fresh one at `version + 1`,
253
+ * re-encrypt every entry under it and re-make every wrapping. Returns the new
254
+ * version, which a caller has to carry in the `credential:key-rotate` frame.
255
+ *
256
+ * `keyID` and every `entryID` survive, so documents referencing them do not
257
+ * move. A wrapping survives only if this device can re-make it: `did`-only
258
+ * ones re-make unattended, and one carrying a secret factor needs that secret
259
+ * back in `params.inputs`, because re-making a wrapping derives its KEK from
260
+ * every surviving factor and a descriptor holds a salt, never a secret.
261
+ *
262
+ * The key, wrappings and entries commit in one store transaction.
263
+ */
264
+ rotate(keyID: string, params?: RotateKeyParams): Promise<number>;
265
+ repairFork(keyID: string, inputs?: FactorInputs): Promise<ForkRepairResult>;
266
+ /** Reconcile records authored by former members of this device's controller. */
267
+ recoverRemovedDevices(params: RecoverRemovedDevicesParams): Promise<number>;
268
+ ensureRemovalRecovery(): Promise<void>;
269
+ revoke(keyID: string, recipientDID: string, params?: RotateKeyParams): Promise<number>;
270
+ /**
271
+ * Address this key to one more recipient: a `did` wrapping over the SAME
272
+ * content key at the key's current version. Every entry already written stays
273
+ * as it is — the recipient opens the key, not a copy of it — and no entry
274
+ * plaintext is touched. Returns the version the wrapping was made at.
275
+ *
276
+ * A recipient that already holds a wrapping is a no-op, so the version alone
277
+ * cannot tell a fresh grant from a re-grant — `created` is what distinguishes
278
+ * them.
279
+ *
280
+ * `recipientWrappableDID` must be RESOLVABLE, not merely a name: a
281
+ * `did:peer:4` short form carries no document and so no agreement key. Working
282
+ * out which string that is for a given party is a group question, and this
283
+ * package does not know what a group is — so it takes the answer rather than
284
+ * asking.
285
+ *
286
+ * `administer`, and the device must be able to open the key: a locked one
287
+ * fails before the first write rather than persisting half a grant.
288
+ */
289
+ grant(keyID: string, recipientWrappableDID: string, params?: GrantKeyParams): Promise<CredentialGrantResult>;
290
+ listKeys(): Promise<Array<string>>;
291
+ /** How many wrappings a key carries — one per way of opening it. */
292
+ countWrappings(keyID: string): Promise<number>;
293
+ deleteKey(keyID: string): Promise<void>;
294
+ putEntry(keyID: string, plaintext: Uint8Array, grant?: GrantEntryParams): Promise<string>;
295
+ updateEntry(address: CredentialEntryAddress, plaintext: Uint8Array): Promise<void>;
296
+ /**
297
+ * As {@link updateEntry}, but never inserts: an entry gone by the time the
298
+ * write lands reports `'absent'` instead of raising. For a writeback racing a
299
+ * concurrent delete, where the caller has no `keyID` to fall back to `putEntry`
300
+ * with and raising would just be a `catch` the caller re-swallows.
301
+ */
302
+ updateEntryIfPresent(address: CredentialEntryAddress, plaintext: Uint8Array): Promise<'updated' | 'absent'>;
303
+ /**
304
+ * As {@link updateEntryIfPresent}, and also only while the entry still carries
305
+ * `expectedHLC`, the stamp its caller read it at. An entry written since reports
306
+ * `'superseded'` and is left as it is.
307
+ */
308
+ updateEntryIfUnchanged(params: UpdateCredentialEntryIfUnchangedParams): Promise<'updated' | 'absent' | 'superseded'>;
309
+ readEntry(address: CredentialEntryAddress): Promise<Uint8Array>;
310
+ getEntryProvenance(address: CredentialEntryAddress): Promise<CredentialEntryProvenance>;
311
+ /**
312
+ * The RAW `owner_did` off the entry's key row — never normalized. Callers
313
+ * comparing against another DID normalize it themselves; folding it here
314
+ * would make the answer wrong for whichever caller wanted the unfolded form.
315
+ * `null` when the entry, or the key it sits on, is absent.
316
+ */
317
+ getEntryKeyOwnerDID(address: CredentialEntryAddress): Promise<string | null>;
318
+ listEntries(keyID: string): Promise<Array<string>>;
319
+ deleteEntry(address: CredentialEntryAddress): Promise<void>;
320
+ /** Refresh this device's older OAuth grant entries after a received key rotation. */
321
+ reencryptGrantEntries(keyID: string): Promise<void>;
322
+ /**
323
+ * Move an entry onto another key: decrypt under the one it sits on, re-encrypt
324
+ * under the target's current version, keep the entry id.
325
+ *
326
+ * The key is the sharing unit, so sharing one secret means moving it onto a
327
+ * shared key rather than sharing the key it already sits under, which would
328
+ * share every other entry beneath that one too. The entry id survives because
329
+ * it never embedded a key id — every document referencing it stays valid. The
330
+ * ciphertext does not survive: it is bound to its key, so this is a genuine
331
+ * re-encryption and the target must be open, not merely named.
332
+ *
333
+ * `administer` on BOTH keys. Writing under the target changes what the target
334
+ * holds, and taking an entry out of the source changes what the source holds.
335
+ */
336
+ reKeyEntry(params: ReKeyCredentialEntryParams): Promise<void>;
337
+ };
@@ -0,0 +1 @@
1
+ export { };
package/lib/manager.d.ts CHANGED
@@ -1,294 +1,3 @@
1
- import { type MethodRegistry, type SigningIdentity } from '@kokuin/token';
2
- import { HLC } from '@kubun/hlc';
3
- import type { CredentialStoreAPI } from '@kubun/store-credential';
4
- import type { Runtime } from '@sozai/runtime';
5
- import { type CredentialAuthority } from './authority.js';
6
- import { type PinThrottleParams } from './pin-throttle.js';
7
- import { type CredentialEntryProvenance } from './provenance.js';
8
- import { type FactorInput, type FactorInputs, type SecretFactorKind } from './suite.js';
9
- export type CreateCredentialManagerParams = {
10
- store: CredentialStoreAPI;
11
- /**
12
- * This device's identity. NOT required to be a `KeyAgreementIdentity`: a device
13
- * that cannot decrypt can still read metadata, and will be able to unwrap by
14
- * passphrase once that factor exists. What it cannot do is take the `did`
15
- * default in {@link CredentialManager.createKey}.
16
- *
17
- * It MUST be able to sign, which a reading identity need not: every row this
18
- * manager writes carries a signed op, and a device that could write rows no
19
- * peer can authenticate would be writing into a lane that has to drop them.
20
- */
21
- identity: SigningIdentity;
22
- /**
23
- * Host runtime, propagated rather than constructed here. This package runs on
24
- * React Native and in browsers as well as on node, and content keys, IVs and
25
- * factor secrets are the values that most need the platform's own CSPRNG
26
- * rather than whatever `crypto` a bundler resolved.
27
- */
28
- runtime: Runtime;
29
- /**
30
- * The device's one clock, passed in rather than built here.
31
- *
32
- * Required, and deliberately not defaulted: a connector builds one manager per
33
- * transaction, so a manager that constructed its own would start from
34
- * `lastWallTime = 0` every time and two of them writing in the same
35
- * millisecond would emit an identical `(wallTime, counter, nodeID)` — a
36
- * colliding merkle leaf and an order tie with no tiebreak.
37
- */
38
- hlc: HLC;
39
- /**
40
- * Who this identity is relative to a key. Defaults to the device answer:
41
- * it administers what it owns and nothing else.
42
- *
43
- * The same port the plugin's query context takes, deliberately — a device
44
- * authority refuses a grant recipient gating someone else's key, and a
45
- * delegation authority will let an agent gate its controller's, without
46
- * either layer growing a predicate of its own.
47
- */
48
- authority?: CredentialAuthority;
49
- /**
50
- * How long an unlocked key stays in memory. Eviction is lazy — checked when
51
- * the cache is read — because a timer would keep the host process alive and
52
- * make every test wait on a clock it does not control.
53
- */
54
- unlockTTL?: number;
55
- /**
56
- * Failed-PIN backoff. Its clock comes from `now`, so a caller cannot give the
57
- * throttle a different notion of time from the cache's.
58
- */
59
- pinThrottle?: Omit<PinThrottleParams, 'now'>;
60
- /** Injectable so a TTL or backoff test does not sleep. */
61
- now?: () => number;
62
- };
63
- export type CreateKeyParams = {
64
- suite?: number;
65
- /** One wrapping, whose factors are an AND. Shorthand for a single-entry `wrappings`. */
66
- factors?: Array<FactorInput>;
67
- /**
68
- * Several wrappings — an OR of ANDs, one per way of opening the key.
69
- *
70
- * A key addressed to two recipients is not two keys and is not a grant: both
71
- * wrappings exist from the first write, which is what a connector needs when
72
- * the account owner and the server that runs sync are different DIDs.
73
- */
74
- wrappings?: Array<Array<FactorInput>>;
75
- /**
76
- * Who may later grant, rotate and revoke. Defaults to this device, which is
77
- * the peer case. A server creating a key on a viewer's behalf passes the
78
- * viewer, and stays a recipient rather than the owner.
79
- */
80
- ownerDID?: string;
81
- /**
82
- * Resolvers for recipient DIDs that carry no key material of their own
83
- * (`did:kokuin:`). Passed per call rather than held on the manager, so a
84
- * request-scoped resolver never outlives its request in the cached manager.
85
- */
86
- methods?: MethodRegistry;
87
- };
88
- export type RotateKeyParams = {
89
- /**
90
- * Migrate to a different ciphersuite. Rotation is the only path between
91
- * suites: it already re-encrypts every entry and re-makes every wrapping,
92
- * which is exactly what a suite change needs.
93
- */
94
- suite?: number;
95
- /**
96
- * Secrets for the surviving factors, keyed by kind — the same prompt
97
- * `addFactor` needs, for the same reason: a descriptor holds a salt and a
98
- * cost, never the secret, so a wrapping carrying one cannot be re-made at the
99
- * new version without it.
100
- *
101
- * Only the rotating identity's own wrappings can carry a secret factor, since
102
- * `addFactor` refuses one on a key it does not administer — so the prompt is
103
- * always one the caller is present for.
104
- */
105
- inputs?: FactorInputs;
106
- /** Resolvers for recipient DIDs that need one (`did:kokuin:`), for re-made `did` wrappings. */
107
- methods?: MethodRegistry;
108
- /**
109
- * Overrides the constructed authority for this one call, so a provider-cached
110
- * manager holds no request state. No production caller feeds one yet — the
111
- * group-grant wiring that will is a follow-up.
112
- */
113
- authority?: CredentialAuthority;
114
- };
115
- /** Optionals shared by the factor-editing verbs `addFactor` and `removeFactor`. */
116
- export type FactorChangeParams = {
117
- /**
118
- * Secrets for the factors that SURVIVE the change, keyed by kind — a
119
- * descriptor holds a salt, never a secret, so the surviving wrapping's KEK
120
- * cannot be re-derived without them.
121
- */
122
- inputs?: FactorInputs;
123
- /** Resolvers for recipient DIDs that need one (`did:kokuin:`), for re-made `did` wrappings. */
124
- methods?: MethodRegistry;
125
- /** Per-call authority overriding the constructed default. See `RotateKeyParams.authority`. */
126
- authority?: CredentialAuthority;
127
- };
128
- /** See `grant`. */
129
- export type GrantKeyParams = {
130
- /** Resolvers for recipient DIDs that carry no key material of their own (`did:kokuin:`). */
131
- methods?: MethodRegistry;
132
- /** Per-call authority overriding the constructed default. See `RotateKeyParams.authority`. */
133
- authority?: CredentialAuthority;
134
- };
135
- /** See `reKeyEntry`. */
136
- export type ReKeyEntryParams = {
137
- /** Per-call authority overriding the constructed default. See `RotateKeyParams.authority`. */
138
- authority?: CredentialAuthority;
139
- };
140
- /**
141
- * Why a key cannot be opened here, as opposed to not being open yet.
142
- *
143
- * Carried so that `status` can answer rather than throw: the one call whose job
144
- * is to report a key as unreadable must not fail on the key that is unreadable.
145
- */
146
- export type CredentialUnopenableReason =
147
- /** A newer peer's ciphersuite. A different build could open it; this one cannot. */
148
- 'unsupported-suite'
149
- /** No wrapping at the key's current version — a rotation that did not finish. */
150
- | 'no-wrapping'
151
- /** Every wrapping belongs to another recipient. Another device can open it. */
152
- | 'not-addressed';
153
- /**
154
- * Derived, never stored. A persisted `unlocked` column would be a belief that
155
- * survives a restart it should not.
156
- *
157
- * `available` is separated from `unlocked` because they differ in what the
158
- * caller must do, which is nothing, and in what the machine must do, which is
159
- * one unwrap. A `did`-only key is `available` until first read, and a TTL
160
- * eviction returns it there rather than to `locked` — no prompt ever becomes
161
- * necessary.
162
- */
163
- export type CredentialKeyStatus = {
164
- state: 'unlocked';
165
- } | {
166
- state: 'available';
167
- } | {
168
- state: 'locked';
169
- /**
170
- * One entry per wrapping that could be opened here: an OR of ANDs. Nested
171
- * because flattening loses the difference between a key openable by a pin
172
- * OR a passphrase and one demanding both, and those are different prompts.
173
- * `did` factors are absent — this device supplies them without asking.
174
- */
175
- unlockOptions: Array<Array<SecretFactorKind>>;
176
- } | {
177
- state: 'unopenable';
178
- reason: CredentialUnopenableReason;
179
- };
180
- export type CredentialGrantResult = {
181
- /** The key version the recipient's wrapping sits at. */
182
- version: number;
183
- /**
184
- * False when the recipient already held a wrapping and nothing was written.
185
- * A caller broadcasting the grant emits its frame either way — that is what
186
- * makes a repeat grant the retry path for a frame that never arrived.
187
- */
188
- created: boolean;
189
- };
190
- export type CredentialManager = {
191
- /** Factor kinds this device can supply. Empty means it can hold no credentials. */
192
- availableFactors(): Array<string>;
193
- /**
194
- * Open a key and hold its content key in memory.
195
- *
196
- * A `did`-only key needs no call: it opens on demand. This is for the ones
197
- * carrying a factor only the user can supply.
198
- */
199
- unlock(keyID: string, inputs?: FactorInputs): Promise<void>;
200
- /** Zero the cached content key. TTL eviction does the same thing. */
201
- lock(keyID: string): void;
202
- lockAll(): void;
203
- status(keyID: string, version?: number): Promise<CredentialKeyStatus>;
204
- /**
205
- * Add a factor to THIS device's wrapping, leaving every other recipient's
206
- * alone. Adding a `pin` therefore gates this device and no other, which is the
207
- * intended granularity.
208
- *
209
- * `params.inputs` must carry the secret for every factor that SURVIVES the
210
- * change, not only the one being added: the new wrapping's key is derived from
211
- * all of them, and a descriptor holds a salt, never a secret.
212
- */
213
- addFactor(keyID: string, factor: FactorInput, params?: FactorChangeParams): Promise<void>;
214
- /** Remove a factor kind from this device's wrapping. See `addFactor` on `params.inputs`. */
215
- removeFactor(keyID: string, kind: string, params?: FactorChangeParams): Promise<void>;
216
- createKey(params?: CreateKeyParams): Promise<string>;
217
- /**
218
- * Retire this key's content key: mint a fresh one at `version + 1`,
219
- * re-encrypt every entry under it and re-make every wrapping. Returns the new
220
- * version, which a caller has to carry in the `credential:key-rotate` frame.
221
- *
222
- * `keyID` and every `entryID` survive, so documents referencing them do not
223
- * move. A wrapping survives only if this device can re-make it: `did`-only
224
- * ones re-make unattended, and one carrying a secret factor needs that secret
225
- * back in `params.inputs`, because re-making a wrapping derives its KEK from
226
- * every surviving factor and a descriptor holds a salt, never a secret.
227
- *
228
- * NOT atomic. This manager holds a resolved store, not a `StoreProvider`, so
229
- * it has no transaction to open — a caller wanting all-or-nothing has to build
230
- * the manager inside one.
231
- */
232
- rotate(keyID: string, params?: RotateKeyParams): Promise<number>;
233
- /**
234
- * Address this key to one more recipient: a `did` wrapping over the SAME
235
- * content key at the key's current version. Every entry already written stays
236
- * as it is — the recipient opens the key, not a copy of it — and no entry
237
- * plaintext is touched. Returns the version the wrapping was made at, which a
238
- * caller has to carry in the `credential:key-grant` frame.
239
- *
240
- * A recipient that already holds a wrapping is a no-op, so the version alone
241
- * cannot tell a fresh grant from a re-grant — `created` is what distinguishes
242
- * them.
243
- *
244
- * `recipientWrappableDID` must be RESOLVABLE, not merely a name: a
245
- * `did:peer:4` short form carries no document and so no agreement key. Working
246
- * out which string that is for a given party is a group question, and this
247
- * package does not know what a group is — so it takes the answer rather than
248
- * asking.
249
- *
250
- * `administer`, and the device must be able to open the key: a locked one
251
- * fails before the first write rather than persisting half a grant.
252
- */
253
- grant(keyID: string, recipientWrappableDID: string, params?: GrantKeyParams): Promise<CredentialGrantResult>;
254
- listKeys(): Promise<Array<string>>;
255
- /** How many wrappings a key carries — one per way of opening it. */
256
- countWrappings(keyID: string): Promise<number>;
257
- deleteKey(keyID: string): Promise<void>;
258
- putEntry(keyID: string, plaintext: Uint8Array): Promise<string>;
259
- updateEntry(entryID: string, plaintext: Uint8Array): Promise<void>;
260
- /**
261
- * As {@link updateEntry}, but never inserts: an entry gone by the time the
262
- * write lands reports `'absent'` instead of raising. For a writeback racing a
263
- * concurrent delete, where the caller has no `keyID` to fall back to `putEntry`
264
- * with and raising would just be a `catch` the caller re-swallows.
265
- */
266
- updateEntryIfPresent(entryID: string, plaintext: Uint8Array): Promise<'updated' | 'absent'>;
267
- readEntry(entryID: string): Promise<Uint8Array>;
268
- getEntryProvenance(entryID: string): Promise<CredentialEntryProvenance>;
269
- /**
270
- * The RAW `owner_did` off the entry's key row — never normalized. Callers
271
- * comparing against another DID normalize it themselves; folding it here
272
- * would make the answer wrong for whichever caller wanted the unfolded form.
273
- * `null` when the entry, or the key it sits on, is absent.
274
- */
275
- getEntryKeyOwnerDID(entryID: string): Promise<string | null>;
276
- listEntries(keyID: string): Promise<Array<string>>;
277
- deleteEntry(entryID: string): Promise<void>;
278
- /**
279
- * Move an entry onto another key: decrypt under the one it sits on, re-encrypt
280
- * under the target's current version, keep the entry id.
281
- *
282
- * The key is the sharing unit, so sharing one secret means moving it onto a
283
- * shared key rather than sharing the key it already sits under, which would
284
- * share every other entry beneath that one too. The entry id survives because
285
- * it never embedded a key id — every document referencing it stays valid. The
286
- * ciphertext does not survive: it is bound to its key, so this is a genuine
287
- * re-encryption and the target must be open, not merely named.
288
- *
289
- * `administer` on BOTH keys. Writing under the target changes what the target
290
- * holds, and taking an entry out of the source changes what the source holds.
291
- */
292
- reKeyEntry(entryID: string, toKeyID: string, params?: ReKeyEntryParams): Promise<void>;
293
- };
1
+ import type { CreateCredentialManagerParams, CredentialManager } from './manager-types.js';
2
+ export type { CreateCredentialManagerParams, CreateKeyParams, CredentialEntryAddress, CredentialGrantResult, CredentialKeyStatus, CredentialManager, CredentialUnopenableReason, FactorChangeParams, GrantEntryParams, GrantKeyParams, RecoverRemovedDevicesParams, ReKeyCredentialEntryParams, ReKeyEntryParams, RotateKeyParams, UpdateCredentialEntryIfUnchangedParams, } from './manager-types.js';
294
3
  export declare function createCredentialManager(params: CreateCredentialManagerParams): CredentialManager;