@interop/wallet-core 0.6.0 → 0.8.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 +31 -3
- package/dist/descriptors/acquire.d.ts +110 -0
- package/dist/descriptors/acquire.d.ts.map +1 -0
- package/dist/descriptors/acquire.js +86 -0
- package/dist/descriptors/acquire.js.map +1 -0
- package/dist/descriptors/cipher.d.ts +56 -0
- package/dist/descriptors/cipher.d.ts.map +1 -0
- package/dist/descriptors/cipher.js +73 -0
- package/dist/descriptors/cipher.js.map +1 -0
- package/dist/descriptors/index.d.ts +32 -0
- package/dist/descriptors/index.d.ts.map +1 -0
- package/dist/descriptors/index.js +31 -0
- package/dist/descriptors/index.js.map +1 -0
- package/dist/descriptors/refresh.d.ts +60 -0
- package/dist/descriptors/refresh.d.ts.map +1 -0
- package/dist/descriptors/refresh.js +67 -0
- package/dist/descriptors/refresh.js.map +1 -0
- package/dist/enrollment/enrollment.d.ts +4 -4
- package/dist/enrollment/enrollment.d.ts.map +1 -1
- package/dist/enrollment/enrollment.js +4 -4
- package/dist/enrollment/enrollment.js.map +1 -1
- package/dist/index.d.ts +9 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -4
- package/dist/index.js.map +1 -1
- package/dist/keys/index.d.ts +7 -6
- package/dist/keys/index.d.ts.map +1 -1
- package/dist/keys/index.js +7 -6
- package/dist/keys/index.js.map +1 -1
- package/dist/keys/pukRoster.d.ts +24 -20
- package/dist/keys/pukRoster.d.ts.map +1 -1
- package/dist/keys/pukRoster.js +27 -24
- package/dist/keys/pukRoster.js.map +1 -1
- package/dist/keys/rosterStore.d.ts +6 -6
- package/dist/keys/rosterStore.d.ts.map +1 -1
- package/dist/keys/rosterStore.js +7 -7
- package/dist/keys/rosterStore.js.map +1 -1
- package/dist/recovery/index.d.ts +31 -0
- package/dist/recovery/index.d.ts.map +1 -0
- package/dist/recovery/index.js +28 -0
- package/dist/recovery/index.js.map +1 -0
- package/dist/recovery/recoveryCode.d.ts +105 -0
- package/dist/recovery/recoveryCode.d.ts.map +1 -0
- package/dist/recovery/recoveryCode.js +155 -0
- package/dist/recovery/recoveryCode.js.map +1 -0
- package/dist/recovery/recoveryRecord.d.ts +75 -0
- package/dist/recovery/recoveryRecord.d.ts.map +1 -0
- package/dist/recovery/recoveryRecord.js +89 -0
- package/dist/recovery/recoveryRecord.js.map +1 -0
- package/dist/recovery/recoveryWebvh.d.ts +126 -0
- package/dist/recovery/recoveryWebvh.d.ts.map +1 -0
- package/dist/recovery/recoveryWebvh.js +379 -0
- package/dist/recovery/recoveryWebvh.js.map +1 -0
- package/dist/space/collections.d.ts +1 -1
- package/dist/space/collections.js +1 -1
- package/dist/sync/collections.d.ts +2 -1
- package/dist/sync/collections.d.ts.map +1 -1
- package/dist/webvh/didWebvh.d.ts +79 -0
- package/dist/webvh/didWebvh.d.ts.map +1 -1
- package/dist/webvh/didWebvh.js +5 -5
- package/dist/webvh/didWebvh.js.map +1 -1
- package/package.json +14 -2
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ identical bytes. It is isomorphic (browser, Node.js, React Native) and has no UI
|
|
|
23
23
|
or storage dependencies -- side effects are injected, and the dep-heavier
|
|
24
24
|
protocol subpaths are import-directly-only.
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
The subpaths:
|
|
27
27
|
|
|
28
28
|
- **`@interop/wallet-core/sync`** -- the Wallet Attached Storage (WAS)
|
|
29
29
|
replication engine core: the `SyncEngine` orchestration (single-flight,
|
|
@@ -61,6 +61,32 @@ Five subpaths:
|
|
|
61
61
|
display helpers and credential input parsing. Raw values out (ISO strings,
|
|
62
62
|
`Date`, booleans); date formatting, i18n, and UI concerns stay in the app.
|
|
63
63
|
|
|
64
|
+
- **`@interop/wallet-core/webvh`** -- the account's did:webvh identity: the
|
|
65
|
+
hosted DID log, its per-client update-key rotation, the client enrollment
|
|
66
|
+
entries, and ZCap signing under the did:webvh verification-method id.
|
|
67
|
+
|
|
68
|
+
- **`@interop/wallet-core/keys`** -- the per-user key (PUK) and its
|
|
69
|
+
`key-map/puk.json` wrap-set roster: minting, the roster's init/read/rotate
|
|
70
|
+
primitives with their client-side guards (`epochsMac`, the latest-seen epoch
|
|
71
|
+
pin, the document-backed recipient resolver), and the roster's
|
|
72
|
+
compare-and-swap descriptor store.
|
|
73
|
+
|
|
74
|
+
- **`@interop/wallet-core/descriptors`** -- collection encryption-descriptor
|
|
75
|
+
acquisition (fetch / cache / offline fallback) and the unknown-epoch refresh
|
|
76
|
+
policy, including a self-refreshing EDV document cipher.
|
|
77
|
+
|
|
78
|
+
- **`@interop/wallet-core/keyring`** -- the unlock layer: the unlock derivation,
|
|
79
|
+
the `{ version, wrapped }` account-pointer record codec, and the unlock Space
|
|
80
|
+
lifecycle.
|
|
81
|
+
|
|
82
|
+
- **`@interop/wallet-core/enrollment`** -- the client enrollment ceremony
|
|
83
|
+
(connect code, approval, completion).
|
|
84
|
+
|
|
85
|
+
- **`@interop/wallet-core/recovery`** -- recovery codes on the roster identity
|
|
86
|
+
model: a code as a minimal always-enrolled wallet client (format and
|
|
87
|
+
derivation, the recovery record, the document half of issuance / revocation /
|
|
88
|
+
recovery).
|
|
89
|
+
|
|
64
90
|
## Install
|
|
65
91
|
|
|
66
92
|
- Node.js 24+ is recommended.
|
|
@@ -91,8 +117,10 @@ import {
|
|
|
91
117
|
```
|
|
92
118
|
|
|
93
119
|
The `sync` and `space` subpaths are re-exported from the package root as well.
|
|
94
|
-
`identity`, `request`,
|
|
95
|
-
|
|
120
|
+
Every other subpath (`identity`, `request`, `display`, `webvh`, `keys`,
|
|
121
|
+
`descriptors`, `keyring`, `enrollment`, `recovery`) is import-directly-only, so
|
|
122
|
+
consumers of the root never pull the signing / KMS / document-loader dependency
|
|
123
|
+
graph.
|
|
96
124
|
|
|
97
125
|
## Contribute
|
|
98
126
|
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Collection encryption-descriptor acquisition: reading a collection's
|
|
6
|
+
* `CollectionEncryption` descriptor (its key-epoch roster) from the Collection
|
|
7
|
+
* Description, caching each success, and falling back to the cached copy when
|
|
8
|
+
* the description cannot be fetched -- offline, a previously-shared collection
|
|
9
|
+
* must keep encrypting under its current epoch. A successful fetch that
|
|
10
|
+
* returns no descriptor (an unshared collection) yields `undefined`: the
|
|
11
|
+
* single-key path.
|
|
12
|
+
*
|
|
13
|
+
* The two seams are deliberately narrow, so a wallet app's own classes satisfy
|
|
14
|
+
* them structurally -- no adapter needed. An {@link EncryptionDescriptorSource}
|
|
15
|
+
* is one signed describe; an {@link EncryptionDescriptorCache} is a durable
|
|
16
|
+
* get/put the host has already scoped to one account's Space (a web wallet: a
|
|
17
|
+
* localStorage pair keyed by Space id; a mobile wallet: a per-(profile,
|
|
18
|
+
* collection) table column), so no scope key appears in the interface.
|
|
19
|
+
*/
|
|
20
|
+
import type { CollectionEncryption, WasClient } from '@interop/was-client';
|
|
21
|
+
/**
|
|
22
|
+
* Where descriptors come from: one signed read of the collection's
|
|
23
|
+
* Description. Resolves `undefined` for a collection that is plaintext or has
|
|
24
|
+
* no descriptor; network errors throw through (callers treat the fetch as
|
|
25
|
+
* best-effort and fall back to a cached copy).
|
|
26
|
+
*/
|
|
27
|
+
export interface EncryptionDescriptorSource {
|
|
28
|
+
collectionEncryption(options: {
|
|
29
|
+
collectionId: string;
|
|
30
|
+
}): Promise<CollectionEncryption | undefined>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Where fetched descriptors survive offline: a durable get/put pre-scoped by
|
|
34
|
+
* the host to one account's Space. `readDescriptor` resolves `undefined` when
|
|
35
|
+
* nothing is cached (never throws for absence).
|
|
36
|
+
*/
|
|
37
|
+
export interface EncryptionDescriptorCache {
|
|
38
|
+
readDescriptor(options: {
|
|
39
|
+
collectionId: string;
|
|
40
|
+
}): Promise<CollectionEncryption | undefined>;
|
|
41
|
+
writeDescriptor(options: {
|
|
42
|
+
collectionId: string;
|
|
43
|
+
descriptor: CollectionEncryption;
|
|
44
|
+
}): Promise<void>;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The {@link EncryptionDescriptorSource} over a was-client handle: reads the
|
|
48
|
+
* collection's Description in the given Space and returns its `encryption`
|
|
49
|
+
* descriptor.
|
|
50
|
+
*
|
|
51
|
+
* @param options {object}
|
|
52
|
+
* @param options.was {WasClient} a client whose signer can read the Space
|
|
53
|
+
* @param options.spaceId {string}
|
|
54
|
+
* @returns {EncryptionDescriptorSource}
|
|
55
|
+
*/
|
|
56
|
+
export declare function wasDescriptorSource({ was, spaceId }: {
|
|
57
|
+
was: WasClient;
|
|
58
|
+
spaceId: string;
|
|
59
|
+
}): EncryptionDescriptorSource;
|
|
60
|
+
/**
|
|
61
|
+
* Acquires one collection's descriptor: fetches it from the source, caching a
|
|
62
|
+
* success; falls back to the cached copy when the fetch fails; and with no
|
|
63
|
+
* source at all (a purely local code path) reads the cache alone. A successful
|
|
64
|
+
* fetch that returns no descriptor resolves `undefined` -- an unshared
|
|
65
|
+
* collection stays on the single-key path -- and deliberately leaves any
|
|
66
|
+
* cached copy in place, mirroring the fetch-failure fallback.
|
|
67
|
+
*
|
|
68
|
+
* @param options {object}
|
|
69
|
+
* @param [options.source] {EncryptionDescriptorSource} omit for cache-only
|
|
70
|
+
* acquisition
|
|
71
|
+
* @param options.cache {EncryptionDescriptorCache}
|
|
72
|
+
* @param options.collectionId {string}
|
|
73
|
+
* @param [options.onFetchError] {function} observes a swallowed fetch
|
|
74
|
+
* failure (the cached-fallback branch); errors from the cache itself throw
|
|
75
|
+
* through
|
|
76
|
+
* @returns {Promise<CollectionEncryption | undefined>}
|
|
77
|
+
*/
|
|
78
|
+
export declare function acquireDescriptor({ source, cache, collectionId, onFetchError }: {
|
|
79
|
+
source?: EncryptionDescriptorSource;
|
|
80
|
+
cache: EncryptionDescriptorCache;
|
|
81
|
+
collectionId: string;
|
|
82
|
+
onFetchError?: (err: unknown, info: {
|
|
83
|
+
collectionId: string;
|
|
84
|
+
}) => void;
|
|
85
|
+
}): Promise<CollectionEncryption | undefined>;
|
|
86
|
+
/**
|
|
87
|
+
* Acquires descriptors for a set of collections concurrently (each fetch is an
|
|
88
|
+
* independent signed round trip, so a session start is not gated on a serial
|
|
89
|
+
* chain of describes). Collections that resolve no descriptor are simply
|
|
90
|
+
* absent from the result.
|
|
91
|
+
*
|
|
92
|
+
* @param options {object}
|
|
93
|
+
* @param [options.source] {EncryptionDescriptorSource} omit for cache-only
|
|
94
|
+
* acquisition
|
|
95
|
+
* @param options.cache {EncryptionDescriptorCache}
|
|
96
|
+
* @param options.collectionIds {string[]}
|
|
97
|
+
* @param [options.onFetchError] {function} observes each swallowed fetch
|
|
98
|
+
* failure
|
|
99
|
+
* @returns {Promise<Record<string, CollectionEncryption>>} keyed by
|
|
100
|
+
* collection id
|
|
101
|
+
*/
|
|
102
|
+
export declare function acquireDescriptors({ source, cache, collectionIds, onFetchError }: {
|
|
103
|
+
source?: EncryptionDescriptorSource;
|
|
104
|
+
cache: EncryptionDescriptorCache;
|
|
105
|
+
collectionIds: string[];
|
|
106
|
+
onFetchError?: (err: unknown, info: {
|
|
107
|
+
collectionId: string;
|
|
108
|
+
}) => void;
|
|
109
|
+
}): Promise<Record<string, CollectionEncryption>>;
|
|
110
|
+
//# sourceMappingURL=acquire.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"acquire.d.ts","sourceRoot":"","sources":["../../src/descriptors/acquire.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,oBAAoB,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAE1E;;;;;GAKG;AACH,MAAM,WAAW,0BAA0B;IACzC,oBAAoB,CAAC,OAAO,EAAE;QAC5B,YAAY,EAAE,MAAM,CAAA;KACrB,GAAG,OAAO,CAAC,oBAAoB,GAAG,SAAS,CAAC,CAAA;CAC9C;AAED;;;;GAIG;AACH,MAAM,WAAW,yBAAyB;IACxC,cAAc,CAAC,OAAO,EAAE;QACtB,YAAY,EAAE,MAAM,CAAA;KACrB,GAAG,OAAO,CAAC,oBAAoB,GAAG,SAAS,CAAC,CAAA;IAC7C,eAAe,CAAC,OAAO,EAAE;QACvB,YAAY,EAAE,MAAM,CAAA;QACpB,UAAU,EAAE,oBAAoB,CAAA;KACjC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAClB;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,GAAG,EACH,OAAO,EACR,EAAE;IACD,GAAG,EAAE,SAAS,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;CAChB,GAAG,0BAA0B,CAU7B;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,iBAAiB,CAAC,EACtC,MAAM,EACN,KAAK,EACL,YAAY,EACZ,YAAY,EACb,EAAE;IACD,MAAM,CAAC,EAAE,0BAA0B,CAAA;IACnC,KAAK,EAAE,yBAAyB,CAAA;IAChC,YAAY,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE;QAAE,YAAY,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAA;CACtE,GAAG,OAAO,CAAC,oBAAoB,GAAG,SAAS,CAAC,CAe5C;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,kBAAkB,CAAC,EACvC,MAAM,EACN,KAAK,EACL,aAAa,EACb,YAAY,EACb,EAAE;IACD,MAAM,CAAC,EAAE,0BAA0B,CAAA;IACnC,KAAK,EAAE,yBAAyB,CAAA;IAChC,aAAa,EAAE,MAAM,EAAE,CAAA;IACvB,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE;QAAE,YAAY,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAA;CACtE,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC,CAiBhD"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The {@link EncryptionDescriptorSource} over a was-client handle: reads the
|
|
3
|
+
* collection's Description in the given Space and returns its `encryption`
|
|
4
|
+
* descriptor.
|
|
5
|
+
*
|
|
6
|
+
* @param options {object}
|
|
7
|
+
* @param options.was {WasClient} a client whose signer can read the Space
|
|
8
|
+
* @param options.spaceId {string}
|
|
9
|
+
* @returns {EncryptionDescriptorSource}
|
|
10
|
+
*/
|
|
11
|
+
export function wasDescriptorSource({ was, spaceId }) {
|
|
12
|
+
return {
|
|
13
|
+
async collectionEncryption({ collectionId }) {
|
|
14
|
+
const description = await was
|
|
15
|
+
.space(spaceId)
|
|
16
|
+
.collection(collectionId)
|
|
17
|
+
.describe();
|
|
18
|
+
return description?.encryption ?? undefined;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Acquires one collection's descriptor: fetches it from the source, caching a
|
|
24
|
+
* success; falls back to the cached copy when the fetch fails; and with no
|
|
25
|
+
* source at all (a purely local code path) reads the cache alone. A successful
|
|
26
|
+
* fetch that returns no descriptor resolves `undefined` -- an unshared
|
|
27
|
+
* collection stays on the single-key path -- and deliberately leaves any
|
|
28
|
+
* cached copy in place, mirroring the fetch-failure fallback.
|
|
29
|
+
*
|
|
30
|
+
* @param options {object}
|
|
31
|
+
* @param [options.source] {EncryptionDescriptorSource} omit for cache-only
|
|
32
|
+
* acquisition
|
|
33
|
+
* @param options.cache {EncryptionDescriptorCache}
|
|
34
|
+
* @param options.collectionId {string}
|
|
35
|
+
* @param [options.onFetchError] {function} observes a swallowed fetch
|
|
36
|
+
* failure (the cached-fallback branch); errors from the cache itself throw
|
|
37
|
+
* through
|
|
38
|
+
* @returns {Promise<CollectionEncryption | undefined>}
|
|
39
|
+
*/
|
|
40
|
+
export async function acquireDescriptor({ source, cache, collectionId, onFetchError }) {
|
|
41
|
+
if (!source) {
|
|
42
|
+
return cache.readDescriptor({ collectionId });
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
const fetched = await source.collectionEncryption({ collectionId });
|
|
46
|
+
if (fetched) {
|
|
47
|
+
await cache.writeDescriptor({ collectionId, descriptor: fetched });
|
|
48
|
+
return fetched;
|
|
49
|
+
}
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
onFetchError?.(err, { collectionId });
|
|
54
|
+
return cache.readDescriptor({ collectionId });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Acquires descriptors for a set of collections concurrently (each fetch is an
|
|
59
|
+
* independent signed round trip, so a session start is not gated on a serial
|
|
60
|
+
* chain of describes). Collections that resolve no descriptor are simply
|
|
61
|
+
* absent from the result.
|
|
62
|
+
*
|
|
63
|
+
* @param options {object}
|
|
64
|
+
* @param [options.source] {EncryptionDescriptorSource} omit for cache-only
|
|
65
|
+
* acquisition
|
|
66
|
+
* @param options.cache {EncryptionDescriptorCache}
|
|
67
|
+
* @param options.collectionIds {string[]}
|
|
68
|
+
* @param [options.onFetchError] {function} observes each swallowed fetch
|
|
69
|
+
* failure
|
|
70
|
+
* @returns {Promise<Record<string, CollectionEncryption>>} keyed by
|
|
71
|
+
* collection id
|
|
72
|
+
*/
|
|
73
|
+
export async function acquireDescriptors({ source, cache, collectionIds, onFetchError }) {
|
|
74
|
+
const resolved = await Promise.all(collectionIds.map(async (collectionId) => [
|
|
75
|
+
collectionId,
|
|
76
|
+
await acquireDescriptor({ source, cache, collectionId, onFetchError })
|
|
77
|
+
]));
|
|
78
|
+
const descriptors = {};
|
|
79
|
+
for (const [collectionId, descriptor] of resolved) {
|
|
80
|
+
if (descriptor) {
|
|
81
|
+
descriptors[collectionId] = descriptor;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return descriptors;
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=acquire.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"acquire.js","sourceRoot":"","sources":["../../src/descriptors/acquire.ts"],"names":[],"mappings":"AAgDA;;;;;;;;;GASG;AACH,MAAM,UAAU,mBAAmB,CAAC,EAClC,GAAG,EACH,OAAO,EAIR;IACC,OAAO;QACL,KAAK,CAAC,oBAAoB,CAAC,EAAE,YAAY,EAAE;YACzC,MAAM,WAAW,GAAG,MAAM,GAAG;iBAC1B,KAAK,CAAC,OAAO,CAAC;iBACd,UAAU,CAAC,YAAY,CAAC;iBACxB,QAAQ,EAAE,CAAA;YACb,OAAO,WAAW,EAAE,UAAU,IAAI,SAAS,CAAA;QAC7C,CAAC;KACF,CAAA;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,EACtC,MAAM,EACN,KAAK,EACL,YAAY,EACZ,YAAY,EAMb;IACC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,KAAK,CAAC,cAAc,CAAC,EAAE,YAAY,EAAE,CAAC,CAAA;IAC/C,CAAC;IACD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,EAAE,YAAY,EAAE,CAAC,CAAA;QACnE,IAAI,OAAO,EAAE,CAAC;YACZ,MAAM,KAAK,CAAC,eAAe,CAAC,EAAE,YAAY,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,CAAA;YAClE,OAAO,OAAO,CAAA;QAChB,CAAC;QACD,OAAO,SAAS,CAAA;IAClB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,YAAY,EAAE,CAAC,GAAG,EAAE,EAAE,YAAY,EAAE,CAAC,CAAA;QACrC,OAAO,KAAK,CAAC,cAAc,CAAC,EAAE,YAAY,EAAE,CAAC,CAAA;IAC/C,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,EACvC,MAAM,EACN,KAAK,EACL,aAAa,EACb,YAAY,EAMb;IACC,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,CAChC,aAAa,CAAC,GAAG,CACf,KAAK,EAAC,YAAY,EAAC,EAAE,CACnB;QACE,YAAY;QACZ,MAAM,iBAAiB,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,CAAC;KAC9D,CACb,CACF,CAAA;IACD,MAAM,WAAW,GAAyC,EAAE,CAAA;IAC5D,KAAK,MAAM,CAAC,YAAY,EAAE,UAAU,CAAC,IAAI,QAAQ,EAAE,CAAC;QAClD,IAAI,UAAU,EAAE,CAAC;YACf,WAAW,CAAC,YAAY,CAAC,GAAG,UAAU,CAAA;QACxC,CAAC;IACH,CAAC;IACD,OAAO,WAAW,CAAA;AACpB,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* A self-refreshing EDV document cipher: `createEdvDocCipher` bound to
|
|
6
|
+
* descriptor acquisition and the once-per-session refresh rule, for a host
|
|
7
|
+
* whose decrypt seam is the cipher itself (a sync engine's `decryptDoc`, a
|
|
8
|
+
* conflict resolver) rather than a row-scanning store.
|
|
9
|
+
*
|
|
10
|
+
* Built, the cipher acquires the collection's descriptor (fetch, cache the
|
|
11
|
+
* success, cached fallback on failure -- see `acquire.ts`) and constructs the
|
|
12
|
+
* underlying EDV cipher from it; with no descriptor, or a descriptor with no
|
|
13
|
+
* epochs, that is the single-key path, unchanged. When a decrypt throws
|
|
14
|
+
* `UnknownEpochError`, the cipher re-acquires the descriptor, rebuilds itself,
|
|
15
|
+
* and retries that decrypt exactly once -- and only once per cipher instance,
|
|
16
|
+
* which the host scopes to one `(profile, collection)` session by dropping
|
|
17
|
+
* its cipher cache when the session ends. A second failure (or any unknown
|
|
18
|
+
* epoch after the one refresh is spent) propagates.
|
|
19
|
+
*/
|
|
20
|
+
import type { IKeyAgreementKey, IKeyResolver } from '@interop/data-integrity-core';
|
|
21
|
+
import { type DocCipher } from '@interop/was-client/edv';
|
|
22
|
+
import { type EncryptionDescriptorCache, type EncryptionDescriptorSource } from './acquire.js';
|
|
23
|
+
/**
|
|
24
|
+
* Builds a {@link DocCipher} whose descriptor is acquired through the
|
|
25
|
+
* source/cache seams and refreshed (once per instance) on an unknown-epoch
|
|
26
|
+
* decrypt.
|
|
27
|
+
*
|
|
28
|
+
* With no `source` the descriptor is served from the cache alone and the
|
|
29
|
+
* refresh path is inert (an unknown-epoch decrypt propagates immediately) --
|
|
30
|
+
* the shape for a purely local code path that must never touch the network.
|
|
31
|
+
*
|
|
32
|
+
* @param options {object}
|
|
33
|
+
* @param options.keyAgreementKey {IKeyAgreementKey} the vault key pair this
|
|
34
|
+
* collection's envelopes are sealed to
|
|
35
|
+
* @param options.keyResolver {IKeyResolver}
|
|
36
|
+
* @param options.collectionId {string}
|
|
37
|
+
* @param [options.idDerivation] {'content' | 'random'} defaults to
|
|
38
|
+
* `'content'`
|
|
39
|
+
* @param [options.source] {EncryptionDescriptorSource}
|
|
40
|
+
* @param options.cache {EncryptionDescriptorCache}
|
|
41
|
+
* @param [options.onFetchError] {function} observes swallowed
|
|
42
|
+
* descriptor-fetch failures
|
|
43
|
+
* @returns {Promise<DocCipher>}
|
|
44
|
+
*/
|
|
45
|
+
export declare function createRefreshingEdvDocCipher({ keyAgreementKey, keyResolver, collectionId, idDerivation, source, cache, onFetchError }: {
|
|
46
|
+
keyAgreementKey: IKeyAgreementKey;
|
|
47
|
+
keyResolver: IKeyResolver;
|
|
48
|
+
collectionId: string;
|
|
49
|
+
idDerivation?: 'content' | 'random';
|
|
50
|
+
source?: EncryptionDescriptorSource;
|
|
51
|
+
cache: EncryptionDescriptorCache;
|
|
52
|
+
onFetchError?: (err: unknown, info: {
|
|
53
|
+
collectionId: string;
|
|
54
|
+
}) => void;
|
|
55
|
+
}): Promise<DocCipher>;
|
|
56
|
+
//# sourceMappingURL=cipher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cipher.d.ts","sourceRoot":"","sources":["../../src/descriptors/cipher.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EACV,gBAAgB,EAChB,YAAY,EACb,MAAM,8BAA8B,CAAA;AACrC,OAAO,EAGL,KAAK,SAAS,EACf,MAAM,yBAAyB,CAAA;AAChC,OAAO,EAEL,KAAK,yBAAyB,EAC9B,KAAK,0BAA0B,EAChC,MAAM,cAAc,CAAA;AAErB;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,4BAA4B,CAAC,EACjD,eAAe,EACf,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,MAAM,EACN,KAAK,EACL,YAAY,EACb,EAAE;IACD,eAAe,EAAE,gBAAgB,CAAA;IACjC,WAAW,EAAE,YAAY,CAAA;IACzB,YAAY,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAA;IACnC,MAAM,CAAC,EAAE,0BAA0B,CAAA;IACnC,KAAK,EAAE,yBAAyB,CAAA;IAChC,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE;QAAE,YAAY,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAA;CACtE,GAAG,OAAO,CAAC,SAAS,CAAC,CAqDrB"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { createEdvDocCipher, UnknownEpochError } from '@interop/was-client/edv';
|
|
2
|
+
import { acquireDescriptor } from './acquire.js';
|
|
3
|
+
/**
|
|
4
|
+
* Builds a {@link DocCipher} whose descriptor is acquired through the
|
|
5
|
+
* source/cache seams and refreshed (once per instance) on an unknown-epoch
|
|
6
|
+
* decrypt.
|
|
7
|
+
*
|
|
8
|
+
* With no `source` the descriptor is served from the cache alone and the
|
|
9
|
+
* refresh path is inert (an unknown-epoch decrypt propagates immediately) --
|
|
10
|
+
* the shape for a purely local code path that must never touch the network.
|
|
11
|
+
*
|
|
12
|
+
* @param options {object}
|
|
13
|
+
* @param options.keyAgreementKey {IKeyAgreementKey} the vault key pair this
|
|
14
|
+
* collection's envelopes are sealed to
|
|
15
|
+
* @param options.keyResolver {IKeyResolver}
|
|
16
|
+
* @param options.collectionId {string}
|
|
17
|
+
* @param [options.idDerivation] {'content' | 'random'} defaults to
|
|
18
|
+
* `'content'`
|
|
19
|
+
* @param [options.source] {EncryptionDescriptorSource}
|
|
20
|
+
* @param options.cache {EncryptionDescriptorCache}
|
|
21
|
+
* @param [options.onFetchError] {function} observes swallowed
|
|
22
|
+
* descriptor-fetch failures
|
|
23
|
+
* @returns {Promise<DocCipher>}
|
|
24
|
+
*/
|
|
25
|
+
export async function createRefreshingEdvDocCipher({ keyAgreementKey, keyResolver, collectionId, idDerivation, source, cache, onFetchError }) {
|
|
26
|
+
const build = async () => createEdvDocCipher({
|
|
27
|
+
keyAgreementKey,
|
|
28
|
+
keyResolver,
|
|
29
|
+
collectionId,
|
|
30
|
+
idDerivation,
|
|
31
|
+
encryption: await acquireDescriptor({
|
|
32
|
+
source,
|
|
33
|
+
cache,
|
|
34
|
+
collectionId,
|
|
35
|
+
onFetchError
|
|
36
|
+
})
|
|
37
|
+
});
|
|
38
|
+
let inner = await build();
|
|
39
|
+
// The one descriptor refresh this cipher instance (= this collection this
|
|
40
|
+
// session) may spend, shared so concurrent unknown-epoch decrypts ride a
|
|
41
|
+
// single re-read instead of each spending one.
|
|
42
|
+
let refreshed = null;
|
|
43
|
+
return {
|
|
44
|
+
encrypt: options => inner.encrypt(options),
|
|
45
|
+
encryptUpdate: options => {
|
|
46
|
+
if (!inner.encryptUpdate) {
|
|
47
|
+
throw new Error(`Collection "${collectionId}" cipher has no in-place update.`);
|
|
48
|
+
}
|
|
49
|
+
return inner.encryptUpdate(options);
|
|
50
|
+
},
|
|
51
|
+
async decrypt({ envelope }) {
|
|
52
|
+
try {
|
|
53
|
+
return await inner.decrypt({ envelope });
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
if (!(err instanceof UnknownEpochError) || !source) {
|
|
57
|
+
throw err;
|
|
58
|
+
}
|
|
59
|
+
refreshed ??= build().then(cipher => {
|
|
60
|
+
inner = cipher;
|
|
61
|
+
});
|
|
62
|
+
await refreshed;
|
|
63
|
+
// One retry under the swapped cipher. If the refresh was already
|
|
64
|
+
// spent before this decrypt began, this re-attempt is a local
|
|
65
|
+
// no-network decrypt that fails the same way -- so a genuinely
|
|
66
|
+
// foreign envelope still surfaces UnknownEpochError, and never a
|
|
67
|
+
// second description read.
|
|
68
|
+
return inner.decrypt({ envelope });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=cipher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cipher.js","sourceRoot":"","sources":["../../src/descriptors/cipher.ts"],"names":[],"mappings":"AAuBA,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EAElB,MAAM,yBAAyB,CAAA;AAChC,OAAO,EACL,iBAAiB,EAGlB,MAAM,cAAc,CAAA;AAErB;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAAC,EACjD,eAAe,EACf,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,MAAM,EACN,KAAK,EACL,YAAY,EASb;IACC,MAAM,KAAK,GAAG,KAAK,IAAwB,EAAE,CAC3C,kBAAkB,CAAC;QACjB,eAAe;QACf,WAAW;QACX,YAAY;QACZ,YAAY;QACZ,UAAU,EAAE,MAAM,iBAAiB,CAAC;YAClC,MAAM;YACN,KAAK;YACL,YAAY;YACZ,YAAY;SACb,CAAC;KACH,CAAC,CAAA;IAEJ,IAAI,KAAK,GAAG,MAAM,KAAK,EAAE,CAAA;IACzB,0EAA0E;IAC1E,yEAAyE;IACzE,+CAA+C;IAC/C,IAAI,SAAS,GAAyB,IAAI,CAAA;IAE1C,OAAO;QACL,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;QAE1C,aAAa,EAAE,OAAO,CAAC,EAAE;YACvB,IAAI,CAAC,KAAK,CAAC,aAAa,EAAE,CAAC;gBACzB,MAAM,IAAI,KAAK,CACb,eAAe,YAAY,kCAAkC,CAC9D,CAAA;YACH,CAAC;YACD,OAAO,KAAK,CAAC,aAAa,CAAC,OAAO,CAAC,CAAA;QACrC,CAAC;QAED,KAAK,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE;YACxB,IAAI,CAAC;gBACH,OAAO,MAAM,KAAK,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAA;YAC1C,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,IAAI,CAAC,CAAC,GAAG,YAAY,iBAAiB,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;oBACnD,MAAM,GAAG,CAAA;gBACX,CAAC;gBACD,SAAS,KAAK,KAAK,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE;oBAClC,KAAK,GAAG,MAAM,CAAA;gBAChB,CAAC,CAAC,CAAA;gBACF,MAAM,SAAS,CAAA;gBACf,iEAAiE;gBACjE,8DAA8D;gBAC9D,+DAA+D;gBAC/D,iEAAiE;gBACjE,2BAA2B;gBAC3B,OAAO,KAAK,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAA;YACpC,CAAC;QACH,CAAC;KACF,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* The `@interop/wallet-core/descriptors` subpath: collection
|
|
6
|
+
* encryption-descriptor acquisition and the unknown-epoch refresh policy --
|
|
7
|
+
* the one implementation of "which key epoch does this collection encrypt
|
|
8
|
+
* under, and when do we ask again" that every wallet replica must share (a
|
|
9
|
+
* drift here does not fail loudly; it fails as a resource one replica cannot
|
|
10
|
+
* decrypt).
|
|
11
|
+
*
|
|
12
|
+
* - `EncryptionDescriptorSource` / `EncryptionDescriptorCache` -- the narrow
|
|
13
|
+
* seams a host implements: one signed Collection Description read, and a
|
|
14
|
+
* durable get/put pre-scoped to one account's Space.
|
|
15
|
+
* - `wasDescriptorSource` -- the `EncryptionDescriptorSource` over a
|
|
16
|
+
* was-client handle.
|
|
17
|
+
* - `acquireDescriptor` / `acquireDescriptors` -- fetch + cache with the
|
|
18
|
+
* cached fallback (offline, a previously-shared collection keeps encrypting
|
|
19
|
+
* under its current epoch; no descriptor at all is the single-key path).
|
|
20
|
+
* - `DescriptorRefreshPolicy` -- the once-per-collection-per-session
|
|
21
|
+
* unknown-epoch refresh guard, plus the refresh-and-re-read-once wrapper
|
|
22
|
+
* for hosts whose reads scan rows and count unknown-epoch skips.
|
|
23
|
+
* - `createRefreshingEdvDocCipher` -- `createEdvDocCipher` bound to both: a
|
|
24
|
+
* cipher that acquires its own descriptor and, on an unknown-epoch decrypt,
|
|
25
|
+
* re-reads the description, swaps itself, and retries exactly once per
|
|
26
|
+
* instance.
|
|
27
|
+
*/
|
|
28
|
+
export { acquireDescriptor, acquireDescriptors, wasDescriptorSource } from './acquire.js';
|
|
29
|
+
export type { EncryptionDescriptorCache, EncryptionDescriptorSource } from './acquire.js';
|
|
30
|
+
export { DescriptorRefreshPolicy } from './refresh.js';
|
|
31
|
+
export { createRefreshingEdvDocCipher } from './cipher.js';
|
|
32
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/descriptors/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACpB,MAAM,cAAc,CAAA;AACrB,YAAY,EACV,yBAAyB,EACzB,0BAA0B,EAC3B,MAAM,cAAc,CAAA;AAErB,OAAO,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAA;AAEtD,OAAO,EAAE,4BAA4B,EAAE,MAAM,aAAa,CAAA"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* The `@interop/wallet-core/descriptors` subpath: collection
|
|
6
|
+
* encryption-descriptor acquisition and the unknown-epoch refresh policy --
|
|
7
|
+
* the one implementation of "which key epoch does this collection encrypt
|
|
8
|
+
* under, and when do we ask again" that every wallet replica must share (a
|
|
9
|
+
* drift here does not fail loudly; it fails as a resource one replica cannot
|
|
10
|
+
* decrypt).
|
|
11
|
+
*
|
|
12
|
+
* - `EncryptionDescriptorSource` / `EncryptionDescriptorCache` -- the narrow
|
|
13
|
+
* seams a host implements: one signed Collection Description read, and a
|
|
14
|
+
* durable get/put pre-scoped to one account's Space.
|
|
15
|
+
* - `wasDescriptorSource` -- the `EncryptionDescriptorSource` over a
|
|
16
|
+
* was-client handle.
|
|
17
|
+
* - `acquireDescriptor` / `acquireDescriptors` -- fetch + cache with the
|
|
18
|
+
* cached fallback (offline, a previously-shared collection keeps encrypting
|
|
19
|
+
* under its current epoch; no descriptor at all is the single-key path).
|
|
20
|
+
* - `DescriptorRefreshPolicy` -- the once-per-collection-per-session
|
|
21
|
+
* unknown-epoch refresh guard, plus the refresh-and-re-read-once wrapper
|
|
22
|
+
* for hosts whose reads scan rows and count unknown-epoch skips.
|
|
23
|
+
* - `createRefreshingEdvDocCipher` -- `createEdvDocCipher` bound to both: a
|
|
24
|
+
* cipher that acquires its own descriptor and, on an unknown-epoch decrypt,
|
|
25
|
+
* re-reads the description, swaps itself, and retries exactly once per
|
|
26
|
+
* instance.
|
|
27
|
+
*/
|
|
28
|
+
export { acquireDescriptor, acquireDescriptors, wasDescriptorSource } from './acquire.js';
|
|
29
|
+
export { DescriptorRefreshPolicy } from './refresh.js';
|
|
30
|
+
export { createRefreshingEdvDocCipher } from './cipher.js';
|
|
31
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/descriptors/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACpB,MAAM,cAAc,CAAA;AAMrB,OAAO,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAA;AAEtD,OAAO,EAAE,4BAA4B,EAAE,MAAM,aAAa,CAAA"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* The unknown-epoch refresh policy: an epoch rotation emits no change-feed
|
|
6
|
+
* entry, so a cipher built from a cached descriptor can meet envelopes stamped
|
|
7
|
+
* with an epoch it has never seen. The remedy is one re-read of the
|
|
8
|
+
* Collection Description plus a cipher rebuild and a single retry -- and the
|
|
9
|
+
* policy guards that remedy to ONCE per collection per session, so a
|
|
10
|
+
* genuinely foreign envelope (one no descriptor will ever route) cannot drive
|
|
11
|
+
* a refetch loop, let alone a refetch per resource.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Tracks which collections have already spent their one refresh this session,
|
|
15
|
+
* and runs reads under the refresh-and-retry-once rule. One instance per
|
|
16
|
+
* session (it IS the session scope of the guard); the injected `refresh` does
|
|
17
|
+
* the host's whole swap -- re-acquire the descriptor(s), rebuild the
|
|
18
|
+
* cipher(s), and install them wherever the host keeps them.
|
|
19
|
+
*/
|
|
20
|
+
export declare class DescriptorRefreshPolicy {
|
|
21
|
+
#private;
|
|
22
|
+
constructor({ refresh }: {
|
|
23
|
+
refresh: (options: {
|
|
24
|
+
collectionId: string;
|
|
25
|
+
}) => Promise<void>;
|
|
26
|
+
});
|
|
27
|
+
/** Whether a collection still has its one refresh this session. */
|
|
28
|
+
shouldRefresh({ collectionId }: {
|
|
29
|
+
collectionId: string;
|
|
30
|
+
}): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Runs a read that reports whether it skipped unknown-epoch rows; on the
|
|
33
|
+
* first such report for a collection this session, spends the collection's
|
|
34
|
+
* refresh (descriptor re-read + cipher swap, via the injected `refresh`) and
|
|
35
|
+
* re-reads once. A later unknown-epoch report for the same collection
|
|
36
|
+
* returns the read's value as-is.
|
|
37
|
+
*
|
|
38
|
+
* @param options {object}
|
|
39
|
+
* @param options.collectionId {string}
|
|
40
|
+
* @param options.read {function} the read, reporting `unknownEpoch`
|
|
41
|
+
* @returns {Promise<T>} the (possibly re-read) value
|
|
42
|
+
*/
|
|
43
|
+
readWithRefresh<T>({ collectionId, read }: {
|
|
44
|
+
collectionId: string;
|
|
45
|
+
read: () => Promise<{
|
|
46
|
+
value: T;
|
|
47
|
+
unknownEpoch: boolean;
|
|
48
|
+
}>;
|
|
49
|
+
}): Promise<T>;
|
|
50
|
+
/**
|
|
51
|
+
* Re-arms the guard -- for one collection, or (with no argument) for all.
|
|
52
|
+
* Call when a fresh descriptor is installed by some other path (a share,
|
|
53
|
+
* unshare, or recipient rotation this session performed itself), since the
|
|
54
|
+
* next unknown-epoch read is then evidence of a NEW rotation elsewhere.
|
|
55
|
+
*/
|
|
56
|
+
reset(options?: {
|
|
57
|
+
collectionId?: string;
|
|
58
|
+
}): void;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=refresh.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refresh.d.ts","sourceRoot":"","sources":["../../src/descriptors/refresh.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;GAQG;AAEH;;;;;;GAMG;AACH,qBAAa,uBAAuB;;gBAKtB,EACV,OAAO,EACR,EAAE;QACD,OAAO,EAAE,CAAC,OAAO,EAAE;YAAE,YAAY,EAAE,MAAM,CAAA;SAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;KAC9D;IAID,mEAAmE;IACnE,aAAa,CAAC,EAAE,YAAY,EAAE,EAAE;QAAE,YAAY,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO;IAIlE;;;;;;;;;;;OAWG;IACG,eAAe,CAAC,CAAC,EAAE,EACvB,YAAY,EACZ,IAAI,EACL,EAAE;QACD,YAAY,EAAE,MAAM,CAAA;QACpB,IAAI,EAAE,MAAM,OAAO,CAAC;YAAE,KAAK,EAAE,CAAC,CAAC;YAAC,YAAY,EAAE,OAAO,CAAA;SAAE,CAAC,CAAA;KACzD,GAAG,OAAO,CAAC,CAAC,CAAC;IAUd;;;;;OAKG;IACH,KAAK,CAAC,OAAO,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;CAOjD"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* The unknown-epoch refresh policy: an epoch rotation emits no change-feed
|
|
6
|
+
* entry, so a cipher built from a cached descriptor can meet envelopes stamped
|
|
7
|
+
* with an epoch it has never seen. The remedy is one re-read of the
|
|
8
|
+
* Collection Description plus a cipher rebuild and a single retry -- and the
|
|
9
|
+
* policy guards that remedy to ONCE per collection per session, so a
|
|
10
|
+
* genuinely foreign envelope (one no descriptor will ever route) cannot drive
|
|
11
|
+
* a refetch loop, let alone a refetch per resource.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Tracks which collections have already spent their one refresh this session,
|
|
15
|
+
* and runs reads under the refresh-and-retry-once rule. One instance per
|
|
16
|
+
* session (it IS the session scope of the guard); the injected `refresh` does
|
|
17
|
+
* the host's whole swap -- re-acquire the descriptor(s), rebuild the
|
|
18
|
+
* cipher(s), and install them wherever the host keeps them.
|
|
19
|
+
*/
|
|
20
|
+
export class DescriptorRefreshPolicy {
|
|
21
|
+
#refresh;
|
|
22
|
+
// Collection ids whose one refresh this session is already spent.
|
|
23
|
+
#refreshed = new Set();
|
|
24
|
+
constructor({ refresh }) {
|
|
25
|
+
this.#refresh = refresh;
|
|
26
|
+
}
|
|
27
|
+
/** Whether a collection still has its one refresh this session. */
|
|
28
|
+
shouldRefresh({ collectionId }) {
|
|
29
|
+
return !this.#refreshed.has(collectionId);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Runs a read that reports whether it skipped unknown-epoch rows; on the
|
|
33
|
+
* first such report for a collection this session, spends the collection's
|
|
34
|
+
* refresh (descriptor re-read + cipher swap, via the injected `refresh`) and
|
|
35
|
+
* re-reads once. A later unknown-epoch report for the same collection
|
|
36
|
+
* returns the read's value as-is.
|
|
37
|
+
*
|
|
38
|
+
* @param options {object}
|
|
39
|
+
* @param options.collectionId {string}
|
|
40
|
+
* @param options.read {function} the read, reporting `unknownEpoch`
|
|
41
|
+
* @returns {Promise<T>} the (possibly re-read) value
|
|
42
|
+
*/
|
|
43
|
+
async readWithRefresh({ collectionId, read }) {
|
|
44
|
+
const first = await read();
|
|
45
|
+
if (first.unknownEpoch && this.shouldRefresh({ collectionId })) {
|
|
46
|
+
this.#refreshed.add(collectionId);
|
|
47
|
+
await this.#refresh({ collectionId });
|
|
48
|
+
return (await read()).value;
|
|
49
|
+
}
|
|
50
|
+
return first.value;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Re-arms the guard -- for one collection, or (with no argument) for all.
|
|
54
|
+
* Call when a fresh descriptor is installed by some other path (a share,
|
|
55
|
+
* unshare, or recipient rotation this session performed itself), since the
|
|
56
|
+
* next unknown-epoch read is then evidence of a NEW rotation elsewhere.
|
|
57
|
+
*/
|
|
58
|
+
reset(options) {
|
|
59
|
+
if (options?.collectionId === undefined) {
|
|
60
|
+
this.#refreshed.clear();
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
this.#refreshed.delete(options.collectionId);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=refresh.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refresh.js","sourceRoot":"","sources":["../../src/descriptors/refresh.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;GAQG;AAEH;;;;;;GAMG;AACH,MAAM,OAAO,uBAAuB;IACzB,QAAQ,CAAsD;IACvE,kEAAkE;IACzD,UAAU,GAAG,IAAI,GAAG,EAAU,CAAA;IAEvC,YAAY,EACV,OAAO,EAGR;QACC,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAA;IACzB,CAAC;IAED,mEAAmE;IACnE,aAAa,CAAC,EAAE,YAAY,EAA4B;QACtD,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,YAAY,CAAC,CAAA;IAC3C,CAAC;IAED;;;;;;;;;;;OAWG;IACH,KAAK,CAAC,eAAe,CAAI,EACvB,YAAY,EACZ,IAAI,EAIL;QACC,MAAM,KAAK,GAAG,MAAM,IAAI,EAAE,CAAA;QAC1B,IAAI,KAAK,CAAC,YAAY,IAAI,IAAI,CAAC,aAAa,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,CAAC;YAC/D,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,YAAY,CAAC,CAAA;YACjC,MAAM,IAAI,CAAC,QAAQ,CAAC,EAAE,YAAY,EAAE,CAAC,CAAA;YACrC,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,KAAK,CAAA;QAC7B,CAAC;QACD,OAAO,KAAK,CAAC,KAAK,CAAA;IACpB,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,OAAmC;QACvC,IAAI,OAAO,EAAE,YAAY,KAAK,SAAS,EAAE,CAAC;YACxC,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAA;QACzB,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,CAAA;QAC9C,CAAC;IACH,CAAC;CACF"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { IKeyAgreementKey } from '@interop/data-integrity-core';
|
|
2
|
-
import type {
|
|
2
|
+
import type { EncryptionDescriptorStore } from '@interop/was-client/edv';
|
|
3
3
|
import type { ClientWebvhUpdateKeys, WebvhEnrollmentKeys, WebvhIdStore } from '../webvh/didWebvh.js';
|
|
4
4
|
import type { Puk } from '../keys/puk.js';
|
|
5
5
|
import type { AccountPointer } from '../keyring/record.js';
|
|
@@ -95,8 +95,8 @@ export declare function mintEnrollmentRequest(): Promise<{
|
|
|
95
95
|
* @param options.clientKeyAgreementKey {IKeyAgreementKey} the approving
|
|
96
96
|
* client's own (identity) key-agreement key, unwrapping each epoch for
|
|
97
97
|
* re-wrapping
|
|
98
|
-
* @param options.pukRosterStore {
|
|
99
|
-
* `key-map/puk.json`
|
|
98
|
+
* @param options.pukRosterStore {EncryptionDescriptorStore} the account's
|
|
99
|
+
* `key-map/puk.json` descriptor store
|
|
100
100
|
* @param options.idStore {WebvhIdStore} the account's `id` collection
|
|
101
101
|
* @returns {Promise<{ did: string }>} the account's did:webvh
|
|
102
102
|
*/
|
|
@@ -104,7 +104,7 @@ export declare function approveEnrollment({ request, clientWebvhKeys, clientKeyA
|
|
|
104
104
|
request: EnrollmentRequest;
|
|
105
105
|
clientWebvhKeys: ClientWebvhUpdateKeys;
|
|
106
106
|
clientKeyAgreementKey: IKeyAgreementKey;
|
|
107
|
-
pukRosterStore:
|
|
107
|
+
pukRosterStore: EncryptionDescriptorStore;
|
|
108
108
|
idStore: WebvhIdStore;
|
|
109
109
|
}): Promise<{
|
|
110
110
|
did: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"enrollment.d.ts","sourceRoot":"","sources":["../../src/enrollment/enrollment.ts"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAA;AACpE,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"enrollment.d.ts","sourceRoot":"","sources":["../../src/enrollment/enrollment.ts"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAA;AACpE,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAA;AASxE,OAAO,KAAK,EACV,qBAAqB,EACrB,mBAAmB,EACnB,YAAY,EACb,MAAM,sBAAsB,CAAA;AAS7B,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAa1D;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,GAAG,mBAAmB,CAAA;AAEnD;;;;GAIG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;gBAE7C,OAAO,SAC+C;CAKzD;AASD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,EACtC,OAAO,EACR,EAAE;IACD,OAAO,EAAE,iBAAiB,CAAA;CAC3B,GAAG,MAAM,CAKT;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,EACrC,IAAI,EACL,EAAE;IACD,IAAI,EAAE,MAAM,CAAA;CACb,GAAG,iBAAiB,CAwDpB;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,OAAO,EACR,EAAE;IACD,OAAO,EAAE,iBAAiB,CAAA;CAC3B,GAAG,MAAM,CAET;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,EACrC,OAAO,EACR,EAAE;IACD,OAAO,EAAE,iBAAiB,CAAA;CAC3B,GAAG,MAAM,CAET;AAED;;;;;;;;;GASG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC;IACrD,UAAU,EAAE,UAAU,CAAA;IACtB,eAAe,EAAE,qBAAqB,CAAA;IACtC,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;CAClB,CAAC,CA2BD;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,iBAAiB,CAAC,EACtC,OAAO,EACP,eAAe,EACf,qBAAqB,EACrB,cAAc,EACd,OAAO,EACR,EAAE;IACD,OAAO,EAAE,iBAAiB,CAAA;IAC1B,eAAe,EAAE,qBAAqB,CAAA;IACtC,qBAAqB,EAAE,gBAAgB,CAAA;IACvC,cAAc,EAAE,yBAAyB,CAAA;IACzC,OAAO,EAAE,YAAY,CAAA;CACtB,GAAG,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAiB3B;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,sBAAsB,CAAC,EAC3C,UAAU,EACV,eAAe,EACf,OAAO,EACR,EAAE;IACD,UAAU,EAAE,UAAU,CAAA;IACtB,eAAe,EAAE,qBAAqB,CAAA;IACtC,OAAO,EAAE,cAAc,CAAA;CACxB,GAAG,OAAO,CAAC;IAAE,GAAG,EAAE,GAAG,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,CAAC,CA2E/C"}
|