@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,421 @@
1
+ import { normalizeDID } from '@kokuin/token';
2
+ import { AmbiguousCredentialWrapping, CredentialKeyLocked, CredentialKeyVersionMismatch, CredentialNotFound, CredentialRepairPending, CredentialSecretRejected, CredentialTampered, CredentialUnopenable, NoAvailableFactor, UnsupportedCredentialSuite } from './errors.js';
3
+ import { isSecretFactorKind } from './manager-context.js';
4
+ import { getSuite, isSupportedSuite } from './suite.js';
5
+ export function createOpenFunctions(ctx, internals) {
6
+ const { store, identity, runtime, authority, decrypting, cache, state, ensureFloored, nextHLC } = ctx;
7
+ const { winningBranch, putRecord, putWrappingRecord, suiteForVersion } = internals.records;
8
+ // Input-bearing opens (a supplied pin/passphrase) never dedup: their outcome
9
+ // and the PIN throttle are caller-specific, so one caller's attempt must not
10
+ // stand in for another's.
11
+ const hasSuppliedInputs = (inputs)=>inputs != null && Object.values(inputs).some((value)=>value != null);
12
+ async function isSelfRevoked(keyID) {
13
+ const self = normalizeDID(identity.id);
14
+ const revoked = (await store.listRevocations(keyID)).some((revocation)=>normalizeDID(revocation.recipient_did) === self);
15
+ if (revoked) cache.evict(keyID);
16
+ return revoked;
17
+ }
18
+ /**
19
+ * Whether this device could ever satisfy a wrapping, given the chance to
20
+ * prompt.
21
+ *
22
+ * This is what separates "locked" from "unopenable", and the two need opposite
23
+ * responses from a caller. `recipient` is normalized at the comparison and
24
+ * never at the write: it is a key-resolution input stored in peer:4 long form,
25
+ * and folding it would make the wrapping unopenable.
26
+ */ function satisfiableFactors(descriptors) {
27
+ const needed = [];
28
+ for (const descriptor of descriptors){
29
+ if (isSecretFactorKind(descriptor.kind)) {
30
+ needed.push(descriptor.kind);
31
+ continue;
32
+ }
33
+ if (descriptor.kind !== 'did') {
34
+ return null;
35
+ }
36
+ const recipient = descriptor.params.recipient;
37
+ if (decrypting == null || typeof recipient !== 'string') {
38
+ return null;
39
+ }
40
+ if (normalizeDID(recipient) !== normalizeDID(identity.id)) {
41
+ return null;
42
+ }
43
+ }
44
+ return needed;
45
+ }
46
+ /**
47
+ * The wrapping this device holds by its own `did`.
48
+ *
49
+ * Not found by `recipient_did`: that column is populated only for the
50
+ * single-`did` headless default, so a `[did, pin]` wrapping has it null. The
51
+ * descriptors are the authority. More than one match throws rather than
52
+ * picking — silently re-wrapping the wrong row would leave the other one
53
+ * un-gated and look like success.
54
+ */ async function ownWrapping(keyID, keyVersion) {
55
+ const wrappings = await store.listWrappings(keyID, keyVersion);
56
+ const mine = wrappings.filter((wrapping)=>satisfiableFactors(wrapping.factors) != null && wrapping.factors.some((descriptor)=>descriptor.kind === 'did' && typeof descriptor.params.recipient === 'string' && normalizeDID(descriptor.params.recipient) === normalizeDID(identity.id)));
57
+ if (mine.length === 0) {
58
+ throw new CredentialNotFound('Credential wrapping for this device on key', keyID);
59
+ }
60
+ if (mine.length > 1) {
61
+ throw new AmbiguousCredentialWrapping(keyID, mine.length);
62
+ }
63
+ return mine[0];
64
+ }
65
+ /**
66
+ * Rebuild wrap-time inputs from stored descriptors.
67
+ *
68
+ * A `did` descriptor carries its recipient, so it reconstructs itself. A
69
+ * secret one carries a salt and a cost but never the secret, so the caller has
70
+ * to supply it again — the new wrapping's key is derived from every surviving
71
+ * factor, not only the one being changed.
72
+ */ function factorsFromDescriptors(descriptors, inputs) {
73
+ return descriptors.map((descriptor)=>{
74
+ if (descriptor.kind === 'did') {
75
+ const recipient = descriptor.params.recipient;
76
+ if (typeof recipient !== 'string') {
77
+ throw new CredentialTampered('did factor descriptor carries no recipient');
78
+ }
79
+ return {
80
+ kind: 'did',
81
+ recipientDID: recipient
82
+ };
83
+ }
84
+ if (!isSecretFactorKind(descriptor.kind)) {
85
+ throw new NoAvailableFactor(descriptor.kind);
86
+ }
87
+ const input = inputs?.[descriptor.kind];
88
+ if (input == null) {
89
+ throw new NoAvailableFactor(descriptor.kind);
90
+ }
91
+ const { m, t, p } = descriptor.params;
92
+ // The cost travels with the factor: re-wrapping is not the moment to
93
+ // silently reset a wrapping that was deliberately made expensive.
94
+ const cost = typeof m === 'number' && typeof t === 'number' && typeof p === 'number' ? {
95
+ m,
96
+ t,
97
+ p
98
+ } : undefined;
99
+ return {
100
+ kind: descriptor.kind,
101
+ input,
102
+ cost
103
+ };
104
+ });
105
+ }
106
+ /**
107
+ * Re-wrap this device's own wrapping under a new factor list.
108
+ *
109
+ * The new row and old tombstone commit together under the key lock. Their
110
+ * order also keeps the old wrapping available until the new one is stored.
111
+ */ async function rewrapOwn({ keyID, inputs, change, methods, authority: recordAuthority = authority }) {
112
+ await ensureFloored();
113
+ const key = await loadKey(keyID);
114
+ const existing = await ownWrapping(keyID, key.version);
115
+ const { contentKey, branchID } = await openKey(keyID, inputs);
116
+ const suite = getSuite(key.suite);
117
+ const wrappingID = runtime.getRandomID();
118
+ // `contentKey` is our own private clone from the cache; zero it once its use — this
119
+ // rewrap — is done, on success or throw. The `try` opens right after the
120
+ // acquisition so the caller-supplied `change` throwing also cannot leak it.
121
+ let wrapped;
122
+ try {
123
+ const factors = change(factorsFromDescriptors(existing.factors, inputs));
124
+ wrapped = await suite.wrap({
125
+ runtime,
126
+ contentKey,
127
+ factors,
128
+ wrappingID,
129
+ keyID,
130
+ keyVersion: key.version,
131
+ methods
132
+ });
133
+ } finally{
134
+ contentKey.fill(0);
135
+ }
136
+ const { descriptors, iv, wrappedKey } = wrapped;
137
+ const replacement = {
138
+ wrappingID,
139
+ keyID,
140
+ keyVersion: key.version,
141
+ factors: descriptors,
142
+ iv,
143
+ wrappedKey,
144
+ recipientDID: existing.recipient_did
145
+ };
146
+ const written = await store.transaction(async (tx)=>{
147
+ await tx.lockKeyRow(keyID);
148
+ const current = await tx.getKey(keyID);
149
+ if (current == null) throw new CredentialNotFound('Credential key', keyID);
150
+ const currentWrapping = (await tx.listWrappings(keyID, key.version)).find((wrapping)=>wrapping.wrapping_id === existing.wrapping_id);
151
+ if (current.version !== key.version || JSON.stringify(currentWrapping) !== JSON.stringify(existing)) {
152
+ return false;
153
+ }
154
+ await putWrappingRecord({
155
+ wrapping: replacement,
156
+ owner: key.owner_did,
157
+ branchID,
158
+ authority: recordAuthority,
159
+ target: tx
160
+ });
161
+ await putRecord({
162
+ type: 'credential.wrapping',
163
+ id: existing.wrapping_id,
164
+ owner: key.owner_did,
165
+ hlc: await nextHLC(),
166
+ payload: null,
167
+ meta: {
168
+ keyID,
169
+ keyVersion: existing.key_version,
170
+ recipient: existing.recipient_did
171
+ },
172
+ target: tx,
173
+ authority: recordAuthority
174
+ });
175
+ return true;
176
+ });
177
+ if (!written) await rewrapOwn({
178
+ keyID,
179
+ inputs,
180
+ change,
181
+ methods,
182
+ authority: recordAuthority
183
+ });
184
+ }
185
+ async function loadKey(keyID, atVersion) {
186
+ const key = await store.getKey(keyID);
187
+ if (key == null) {
188
+ throw new CredentialNotFound('Credential key', keyID);
189
+ }
190
+ if (!isSupportedSuite(key.suite) && (atVersion === undefined || atVersion === key.version)) {
191
+ throw new UnsupportedCredentialSuite(key.suite);
192
+ }
193
+ return key;
194
+ }
195
+ /**
196
+ * Recover a key's content key by trying its wrappings.
197
+ *
198
+ * The wrappings are an OR: any one opening is enough. If none opens, which
199
+ * failure this is depends on whether any of them COULD have opened with input
200
+ * this device is able to ask for — locked and unopenable are the same event
201
+ * with opposite remedies.
202
+ */ /**
203
+ * Recover a key's content key. Wrapped by `openKey`, which adds the input-free
204
+ * dedup; this is the actual open. Captures the lock generation and epoch at
205
+ * entry so a `lock`/`lockAll` that lands before the terminal `cache.set` can
206
+ * suppress it — the key is still returned to this caller, but the cache is not
207
+ * re-warmed behind a lock.
208
+ */ async function doOpenKey({ keyID, inputs, genAtStart, epochAtStart, atVersion }) {
209
+ // `genAtStart`/`epochAtStart` are captured by `openKey` at the moment the
210
+ // caller entered — before any dedup wait — so a lock that lands while a
211
+ // waiter is queued still suppresses that waiter's eventual re-warm.
212
+ const lockedSince = ()=>(state.keyGeneration.get(keyID) ?? 0) !== genAtStart || state.lockAllEpoch !== epochAtStart;
213
+ const key = await loadKey(keyID, atVersion);
214
+ const version = atVersion ?? key.version;
215
+ if (version > key.version) {
216
+ throw new CredentialKeyVersionMismatch(keyID, version, key.version);
217
+ }
218
+ if (await isSelfRevoked(keyID)) {
219
+ throw new CredentialUnopenable(keyID, 'recipient revoked');
220
+ }
221
+ const branchRows = await store.listKeyBranches(keyID, version);
222
+ const proven = new Set(branchRows.map((branch)=>branch.branch_id));
223
+ const winner = proven.size >= 2 ? await winningBranch(branchRows) : null;
224
+ const cached = cache.get(keyID, version);
225
+ if (cached != null) {
226
+ if (winner == null || cached.branchID === winner) {
227
+ return {
228
+ contentKey: cached.contentKey,
229
+ version,
230
+ branchID: cached.branchID
231
+ };
232
+ }
233
+ // A fork-losing hit: `cached.contentKey` is our private clone that
234
+ // we are about to discard for a re-unwrap, so zero it rather than orphan it.
235
+ cached.contentKey.fill(0);
236
+ cache.evictVersion(keyID, version);
237
+ }
238
+ const suite = getSuite(await suiteForVersion(keyID, version, key.suite));
239
+ const wrappings = await store.listWrappingsWithBranch(keyID, version);
240
+ if (wrappings.length === 0) {
241
+ throw new CredentialUnopenable(keyID, `no wrapping at version ${version}`);
242
+ }
243
+ // Concurrent rotations leave two content-key branches under one version. The
244
+ // labels a keyless party could already partition by resolve, here, which one
245
+ // this device opens — so every device converges on the same branch instead
246
+ // of the first-listed wrapping, which is order-dependent and diverges. An
247
+ // empty label is branch-agnostic (legacy/non-rotation state), not a branch of
248
+ // its own, so it never counts toward a fork.
249
+ // A label no key-op vouches for cannot be allowed to win or to open.
250
+ const eligible = wrappings.filter((wrapping)=>wrapping.branch_id === '' || proven.has(wrapping.branch_id));
251
+ // No fork: try every wrapping in listed order, unchanged. Fork: only the
252
+ // winner-labelled wrappings, with empty-label ones as a last-resort fallback
253
+ // — a loser-labelled wrapping opens the wrong content key and is skipped.
254
+ const toTry = winner == null ? eligible : [
255
+ ...eligible.filter((wrapping)=>wrapping.branch_id === winner),
256
+ ...eligible.filter((wrapping)=>wrapping.branch_id === '')
257
+ ];
258
+ const reasons = [];
259
+ const promptable = new Set();
260
+ let rejected = null;
261
+ for (const wrapping of toTry){
262
+ const satisfiable = satisfiableFactors(wrapping.factors);
263
+ if (satisfiable == null) {
264
+ reasons.push(`wrapping ${wrapping.wrapping_id}: not addressed to this device`);
265
+ continue;
266
+ }
267
+ try {
268
+ const contentKey = await suite.unwrap({
269
+ identity: decrypting,
270
+ descriptors: wrapping.factors,
271
+ inputs,
272
+ iv: new Uint8Array(wrapping.iv),
273
+ wrappedKey: new Uint8Array(wrapping.wrapped_key),
274
+ wrappingID: wrapping.wrapping_id,
275
+ keyID,
276
+ keyVersion: wrapping.key_version
277
+ });
278
+ // In a fork every device names the same winner keylessly, so caching the
279
+ // winner as the resolved branch converges post-fork writes even when an
280
+ // empty-label fallback wrapping is what actually opened.
281
+ const branchID = winner ?? wrapping.branch_id;
282
+ // Suppress the re-warm if a lock landed while we were unwrapping: the
283
+ // caller still gets its key, but the cache stays cleared.
284
+ if (!lockedSince()) {
285
+ cache.set(keyID, contentKey, version, branchID);
286
+ }
287
+ return {
288
+ contentKey,
289
+ version,
290
+ branchID
291
+ };
292
+ } catch (error) {
293
+ reasons.push(error instanceof Error ? error.message : String(error));
294
+ if (error instanceof CredentialSecretRejected) {
295
+ rejected = error;
296
+ }
297
+ for (const kind of satisfiable){
298
+ promptable.add(kind);
299
+ }
300
+ }
301
+ }
302
+ // A secret WAS given and was wrong. Distinct from the case below, and it has
303
+ // to survive: a caller retries on one and prompts on the other, and the PIN
304
+ // throttle only counts an attempt that was actually made.
305
+ if (rejected != null) {
306
+ throw rejected;
307
+ }
308
+ // A wrapping this device is addressed by, that failed only for want of a
309
+ // secret nobody was asked for, is a prompt — not a dead end.
310
+ if (promptable.size > 0) {
311
+ throw new CredentialKeyLocked(keyID, [
312
+ ...promptable
313
+ ]);
314
+ }
315
+ if (winner !== null && eligible.some((wrapping)=>wrapping.branch_id !== winner && satisfiableFactors(wrapping.factors) !== null)) {
316
+ throw new CredentialRepairPending(keyID);
317
+ }
318
+ throw new CredentialUnopenable(keyID, reasons.join('; '));
319
+ }
320
+ /**
321
+ * Open a key, collapsing concurrent input-free opens of the same key into one
322
+ * unwrap. A waiter shares only the fact that an open ran, then takes its own
323
+ * clone from the cache; on a miss — the leader hit a lock and skipped its set,
324
+ * or failed — it falls through to its own open. Input-bearing opens never join
325
+ * the herd (see `hasSuppliedInputs`).
326
+ */ async function openKey(keyID, inputs, atVersion) {
327
+ // Captured once, at entry, so a lock landing while this call is queued behind
328
+ // another open still counts against it (see `doOpenKey`).
329
+ const genAtStart = state.keyGeneration.get(keyID) ?? 0;
330
+ const epochAtStart = state.lockAllEpoch;
331
+ if (hasSuppliedInputs(inputs)) {
332
+ return doOpenKey({
333
+ keyID,
334
+ inputs,
335
+ genAtStart,
336
+ epochAtStart,
337
+ atVersion
338
+ });
339
+ }
340
+ const flightID = atVersion === undefined ? keyID : `${keyID}:v${atVersion}`;
341
+ // Join any open already in flight for this key; on a miss, re-contend for
342
+ // leadership rather than opening in parallel — so post-miss opens (a leader
343
+ // that failed or was lock-suppressed) serialize into one leader at a time
344
+ // instead of re-forming the herd.
345
+ while(true){
346
+ const pending = state.inFlight.get(flightID);
347
+ if (pending == null) {
348
+ break;
349
+ }
350
+ await pending;
351
+ if (await isSelfRevoked(keyID)) {
352
+ throw new CredentialUnopenable(keyID, 'recipient revoked');
353
+ }
354
+ const version = atVersion ?? (await loadKey(keyID)).version;
355
+ const hit = cache.get(keyID, version);
356
+ if (hit != null) {
357
+ return {
358
+ contentKey: hit.contentKey,
359
+ version,
360
+ branchID: hit.branchID
361
+ };
362
+ }
363
+ }
364
+ const run = doOpenKey({
365
+ keyID,
366
+ inputs,
367
+ genAtStart,
368
+ epochAtStart,
369
+ atVersion
370
+ });
371
+ // A gate that never rejects, so a waiter's `await pending` cannot throw the
372
+ // leader's error into the wrong caller; the waiter re-derives its own result
373
+ // from the cache (or its own open) after the gate settles.
374
+ const gate = run.then(()=>undefined, ()=>undefined);
375
+ state.inFlight.set(flightID, gate);
376
+ try {
377
+ return await run;
378
+ } finally{
379
+ // Only clear our own entry — a later leader may already have replaced it.
380
+ if (state.inFlight.get(flightID) === gate) {
381
+ state.inFlight.delete(flightID);
382
+ }
383
+ }
384
+ }
385
+ async function openSpecificBranch({ keyID, version, branchID, suiteID, inputs }) {
386
+ const suite = getSuite(suiteID);
387
+ for (const wrapping of (await store.listWrappingsWithBranch(keyID, version))){
388
+ const needed = satisfiableFactors(wrapping.factors);
389
+ if (wrapping.branch_id !== branchID || needed === null || needed.some((kind)=>inputs?.[kind] == null)) {
390
+ continue;
391
+ }
392
+ try {
393
+ return await suite.unwrap({
394
+ identity: decrypting,
395
+ descriptors: wrapping.factors,
396
+ inputs,
397
+ iv: new Uint8Array(wrapping.iv),
398
+ wrappedKey: new Uint8Array(wrapping.wrapped_key),
399
+ wrappingID: wrapping.wrapping_id,
400
+ keyID,
401
+ keyVersion: version
402
+ });
403
+ } catch {
404
+ // Another recipient's wrapping or corrupt local material cannot open this branch.
405
+ }
406
+ }
407
+ return null;
408
+ }
409
+ return {
410
+ isSelfRevoked,
411
+ satisfiableFactors,
412
+ ownWrapping,
413
+ factorsFromDescriptors,
414
+ loadKey,
415
+ doOpenKey,
416
+ openKey,
417
+ openSpecificBranch,
418
+ rewrapOwn,
419
+ hasSuppliedInputs
420
+ };
421
+ }
@@ -0,0 +1,94 @@
1
+ import { type SignedRecord } from '@kubun/records';
2
+ import type { CredentialFactorDescriptor, CredentialRecordPayloads, CredentialRecordType, CredentialStoreAPI, PutCredentialKeyParams } from '@kubun/store-credential';
3
+ import type { CredentialAuthority } from './authority.js';
4
+ import type { ManagerContext } from './manager-context.js';
5
+ import type { GrantEntryParams } from './manager-types.js';
6
+ type PutRecordParams<T extends CredentialRecordType> = {
7
+ type: T;
8
+ id: string;
9
+ owner: string;
10
+ hlc: string;
11
+ payload: CredentialRecordPayloads[T] | null;
12
+ meta: Record<string, unknown>;
13
+ target?: CredentialStoreAPI;
14
+ authority?: CredentialAuthority;
15
+ };
16
+ type PutKeyRecordParams = {
17
+ key: PutCredentialKeyParams;
18
+ keyUnique: string;
19
+ hlc: string;
20
+ authority?: CredentialAuthority;
21
+ target?: CredentialStoreAPI;
22
+ repairDigest?: string;
23
+ };
24
+ type BranchRecordIDParams = {
25
+ keyID: string;
26
+ version: number;
27
+ branchID: string;
28
+ target?: CredentialStoreAPI;
29
+ };
30
+ type PutWrappingRecordParams = {
31
+ wrapping: {
32
+ wrappingID: string;
33
+ keyID: string;
34
+ keyVersion: number;
35
+ factors: Array<CredentialFactorDescriptor>;
36
+ iv: Uint8Array;
37
+ wrappedKey: Uint8Array;
38
+ recipientDID?: string | null;
39
+ };
40
+ owner: string;
41
+ branchID: string;
42
+ branchRecordID?: string;
43
+ authority?: CredentialAuthority;
44
+ target?: CredentialStoreAPI;
45
+ };
46
+ type PutEntryRecordParams = {
47
+ entry: {
48
+ entryID: string;
49
+ keyID: string;
50
+ keyVersion: number;
51
+ iv: Uint8Array;
52
+ ciphertext: Uint8Array;
53
+ hlc: string;
54
+ };
55
+ owner: string;
56
+ branchID: string;
57
+ contentHLC?: string;
58
+ target?: CredentialStoreAPI;
59
+ authority?: CredentialAuthority;
60
+ grant?: GrantEntryParams;
61
+ };
62
+ type EntryGrantInfo = {
63
+ grantDeviceDID: string;
64
+ grantName: string;
65
+ grantOwnerDID: string | undefined;
66
+ };
67
+ export type RecordFunctions = {
68
+ storedKeyRecord: (keyID: string) => Promise<SignedRecord | null>;
69
+ entryRecord: (entryID: string, target?: CredentialStoreAPI, ownerDID?: string) => Promise<SignedRecord | null>;
70
+ winningBranch: (branches: ReadonlyArray<{
71
+ branch_id: string;
72
+ op_jwt: string;
73
+ }>) => Promise<string | null>;
74
+ putRecord: <T extends CredentialRecordType>(args: PutRecordParams<T>) => Promise<SignedRecord & {
75
+ type: T;
76
+ }>;
77
+ keyRecord: (args: PutKeyRecordParams) => Promise<{
78
+ branchID: string;
79
+ branchRecordID: string;
80
+ }>;
81
+ keyUnique: (keyID: string) => Promise<string>;
82
+ branchRecordID: (args: BranchRecordIDParams) => Promise<string>;
83
+ putWrappingRecord: (args: PutWrappingRecordParams) => Promise<void>;
84
+ putEntryRecord: (args: PutEntryRecordParams) => Promise<void>;
85
+ entryGrant: (entryID: string, target?: CredentialStoreAPI, ownerDID?: string) => Promise<EntryGrantInfo | undefined>;
86
+ writableGrant: (entryID: string, ownerDID?: string) => Promise<EntryGrantInfo | undefined>;
87
+ branchProofs: (keyID: string, version: number) => Promise<Map<string, {
88
+ suite: number;
89
+ repairDigest?: string;
90
+ }>>;
91
+ suiteForVersion: (keyID: string, version: number, currentSuite: number) => Promise<number>;
92
+ };
93
+ export declare function createRecordFunctions(ctx: ManagerContext): RecordFunctions;
94
+ export {};