cojson 0.20.7 → 0.20.9
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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +26 -0
- package/dist/SyncStateManager.d.ts.map +1 -1
- package/dist/SyncStateManager.js +0 -2
- package/dist/SyncStateManager.js.map +1 -1
- package/dist/base64url.d.ts +15 -0
- package/dist/base64url.d.ts.map +1 -1
- package/dist/base64url.js +101 -5
- package/dist/base64url.js.map +1 -1
- package/dist/base64url.test.js +76 -1
- package/dist/base64url.test.js.map +1 -1
- package/dist/coValue.d.ts +2 -1
- package/dist/coValue.d.ts.map +1 -1
- package/dist/coValue.js.map +1 -1
- package/dist/coValueCore/coValueCore.d.ts +9 -11
- package/dist/coValueCore/coValueCore.d.ts.map +1 -1
- package/dist/coValueCore/coValueCore.js +92 -65
- package/dist/coValueCore/coValueCore.js.map +1 -1
- package/dist/coValueCore/verifiedState.d.ts +38 -7
- package/dist/coValueCore/verifiedState.d.ts.map +1 -1
- package/dist/coValueCore/verifiedState.js +226 -30
- package/dist/coValueCore/verifiedState.js.map +1 -1
- package/dist/coValues/binaryCoStream.d.ts +63 -0
- package/dist/coValues/binaryCoStream.d.ts.map +1 -0
- package/dist/coValues/binaryCoStream.js +125 -0
- package/dist/coValues/binaryCoStream.js.map +1 -0
- package/dist/coValues/coList.d.ts +3 -1
- package/dist/coValues/coList.d.ts.map +1 -1
- package/dist/coValues/coList.js +15 -6
- package/dist/coValues/coList.js.map +1 -1
- package/dist/coValues/coMap.d.ts +1 -1
- package/dist/coValues/coMap.d.ts.map +1 -1
- package/dist/coValues/coMap.js +2 -2
- package/dist/coValues/coMap.js.map +1 -1
- package/dist/coValues/coStream.d.ts +0 -38
- package/dist/coValues/coStream.d.ts.map +1 -1
- package/dist/coValues/coStream.js +0 -86
- package/dist/coValues/coStream.js.map +1 -1
- package/dist/coValues/group.d.ts +44 -6
- package/dist/coValues/group.d.ts.map +1 -1
- package/dist/coValues/group.js +198 -17
- package/dist/coValues/group.js.map +1 -1
- package/dist/coreToCoValue.d.ts +2 -1
- package/dist/coreToCoValue.d.ts.map +1 -1
- package/dist/coreToCoValue.js +2 -1
- package/dist/coreToCoValue.js.map +1 -1
- package/dist/crypto/NapiCrypto.d.ts +18 -24
- package/dist/crypto/NapiCrypto.d.ts.map +1 -1
- package/dist/crypto/NapiCrypto.js +98 -60
- package/dist/crypto/NapiCrypto.js.map +1 -1
- package/dist/crypto/RNCrypto.d.ts +16 -3
- package/dist/crypto/RNCrypto.d.ts.map +1 -1
- package/dist/crypto/RNCrypto.js +117 -54
- package/dist/crypto/RNCrypto.js.map +1 -1
- package/dist/crypto/WasmCrypto.d.ts +18 -24
- package/dist/crypto/WasmCrypto.d.ts.map +1 -1
- package/dist/crypto/WasmCrypto.js +100 -61
- package/dist/crypto/WasmCrypto.js.map +1 -1
- package/dist/crypto/crypto.d.ts +55 -19
- package/dist/crypto/crypto.d.ts.map +1 -1
- package/dist/crypto/crypto.js +14 -3
- package/dist/crypto/crypto.js.map +1 -1
- package/dist/exports.d.ts +7 -3
- package/dist/exports.d.ts.map +1 -1
- package/dist/exports.js +4 -2
- package/dist/exports.js.map +1 -1
- package/dist/localNode.d.ts +3 -1
- package/dist/localNode.d.ts.map +1 -1
- package/dist/localNode.js +10 -3
- package/dist/localNode.js.map +1 -1
- package/dist/media.d.ts +1 -1
- package/dist/media.d.ts.map +1 -1
- package/dist/permissions.d.ts +2 -1
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +19 -3
- package/dist/permissions.js.map +1 -1
- package/dist/storage/sqliteAsync/client.d.ts +24 -12
- package/dist/storage/sqliteAsync/client.d.ts.map +1 -1
- package/dist/storage/sqliteAsync/client.js +70 -58
- package/dist/storage/sqliteAsync/client.js.map +1 -1
- package/dist/storage/sqliteAsync/types.d.ts +1 -1
- package/dist/storage/sqliteAsync/types.d.ts.map +1 -1
- package/dist/storage/types.d.ts +1 -0
- package/dist/storage/types.d.ts.map +1 -1
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +7 -1
- package/dist/sync.js.map +1 -1
- package/dist/tests/CojsonMessageChannel.test.js +2 -2
- package/dist/tests/SQLiteClientAsync.test.d.ts +2 -0
- package/dist/tests/SQLiteClientAsync.test.d.ts.map +1 -0
- package/dist/tests/SQLiteClientAsync.test.js +64 -0
- package/dist/tests/SQLiteClientAsync.test.js.map +1 -0
- package/dist/tests/StorageApiAsync.test.js +2 -8
- package/dist/tests/StorageApiAsync.test.js.map +1 -1
- package/dist/tests/SyncStateManager.test.js +2 -2
- package/dist/tests/WasmCrypto.test.js +1 -15
- package/dist/tests/WasmCrypto.test.js.map +1 -1
- package/dist/tests/coList.test.js +24 -5
- package/dist/tests/coList.test.js.map +1 -1
- package/dist/tests/coStream.test.js +4 -3
- package/dist/tests/coStream.test.js.map +1 -1
- package/dist/tests/coValueCore.initTransaction.test.d.ts +2 -0
- package/dist/tests/coValueCore.initTransaction.test.d.ts.map +1 -0
- package/dist/tests/coValueCore.initTransaction.test.js +438 -0
- package/dist/tests/coValueCore.initTransaction.test.js.map +1 -0
- package/dist/tests/coValueCore.test.js +11 -19
- package/dist/tests/coValueCore.test.js.map +1 -1
- package/dist/tests/crypto.test.js +83 -0
- package/dist/tests/crypto.test.js.map +1 -1
- package/dist/tests/deleteCoValue.test.js +5 -5
- package/dist/tests/deleteCoValue.test.js.map +1 -1
- package/dist/tests/group.inheritance.test.js +11 -0
- package/dist/tests/group.inheritance.test.js.map +1 -1
- package/dist/tests/group.test.js +24 -1
- package/dist/tests/group.test.js.map +1 -1
- package/dist/tests/groupSealer.test.d.ts +2 -0
- package/dist/tests/groupSealer.test.d.ts.map +1 -0
- package/dist/tests/groupSealer.test.js +913 -0
- package/dist/tests/groupSealer.test.js.map +1 -0
- package/dist/tests/setup.js +5 -0
- package/dist/tests/setup.js.map +1 -1
- package/dist/tests/sync.auth.test.js +10 -10
- package/dist/tests/sync.concurrentLoad.test.js +12 -12
- package/dist/tests/sync.deleted.test.js +8 -8
- package/dist/tests/sync.garbageCollection.test.js +10 -10
- package/dist/tests/sync.invite.test.js +12 -12
- package/dist/tests/sync.known.test.js +2 -2
- package/dist/tests/sync.load.test.js +107 -107
- package/dist/tests/sync.mesh.test.js +164 -46
- package/dist/tests/sync.mesh.test.js.map +1 -1
- package/dist/tests/sync.multipleServers.test.js +43 -43
- package/dist/tests/sync.peerReconciliation.test.js +29 -29
- package/dist/tests/sync.sharding.test.js +3 -3
- package/dist/tests/sync.storage.test.js +104 -104
- package/dist/tests/sync.storage.test.js.map +1 -1
- package/dist/tests/sync.storageAsync.test.js +56 -56
- package/dist/tests/sync.upload.test.js +22 -22
- package/dist/tests/testStorage.d.ts +2 -0
- package/dist/tests/testStorage.d.ts.map +1 -1
- package/dist/tests/testStorage.js +30 -6
- package/dist/tests/testStorage.js.map +1 -1
- package/dist/typeUtils/isCoValue.js +1 -1
- package/dist/typeUtils/isCoValue.js.map +1 -1
- package/package.json +4 -4
- package/src/SyncStateManager.ts +0 -2
- package/src/base64url.test.ts +89 -1
- package/src/base64url.ts +134 -6
- package/src/coValue.ts +2 -1
- package/src/coValueCore/coValueCore.ts +126 -84
- package/src/coValueCore/verifiedState.ts +335 -53
- package/src/coValues/binaryCoStream.ts +217 -0
- package/src/coValues/coList.ts +21 -8
- package/src/coValues/coMap.ts +3 -0
- package/src/coValues/coStream.ts +0 -170
- package/src/coValues/group.ts +270 -21
- package/src/coreToCoValue.ts +2 -1
- package/src/crypto/NapiCrypto.ts +198 -95
- package/src/crypto/RNCrypto.ts +229 -102
- package/src/crypto/WasmCrypto.ts +201 -95
- package/src/crypto/crypto.ts +118 -45
- package/src/exports.ts +11 -5
- package/src/localNode.ts +17 -1
- package/src/media.ts +1 -1
- package/src/permissions.ts +30 -7
- package/src/storage/sqliteAsync/client.ts +136 -115
- package/src/storage/sqliteAsync/types.ts +3 -1
- package/src/storage/types.ts +4 -0
- package/src/sync.ts +10 -1
- package/src/tests/CojsonMessageChannel.test.ts +2 -2
- package/src/tests/SQLiteClientAsync.test.ts +75 -0
- package/src/tests/StorageApiAsync.test.ts +4 -9
- package/src/tests/SyncStateManager.test.ts +2 -2
- package/src/tests/WasmCrypto.test.ts +1 -25
- package/src/tests/coList.test.ts +39 -5
- package/src/tests/coStream.test.ts +4 -5
- package/src/tests/coValueCore.initTransaction.test.ts +836 -0
- package/src/tests/coValueCore.test.ts +11 -22
- package/src/tests/crypto.test.ts +107 -0
- package/src/tests/deleteCoValue.test.ts +5 -5
- package/src/tests/group.inheritance.test.ts +16 -0
- package/src/tests/group.test.ts +29 -1
- package/src/tests/groupSealer.test.ts +1473 -0
- package/src/tests/setup.ts +6 -0
- package/src/tests/sync.auth.test.ts +10 -10
- package/src/tests/sync.concurrentLoad.test.ts +12 -12
- package/src/tests/sync.deleted.test.ts +8 -8
- package/src/tests/sync.garbageCollection.test.ts +10 -10
- package/src/tests/sync.invite.test.ts +12 -12
- package/src/tests/sync.known.test.ts +2 -2
- package/src/tests/sync.load.test.ts +107 -107
- package/src/tests/sync.mesh.test.ts +189 -46
- package/src/tests/sync.multipleServers.test.ts +43 -43
- package/src/tests/sync.peerReconciliation.test.ts +29 -29
- package/src/tests/sync.sharding.test.ts +3 -3
- package/src/tests/sync.storage.test.ts +104 -104
- package/src/tests/sync.storageAsync.test.ts +56 -56
- package/src/tests/sync.upload.test.ts +22 -22
- package/src/tests/testStorage.ts +39 -9
- package/src/typeUtils/isCoValue.ts +1 -1
- package/dist/coValueCore/SessionMap.d.ts +0 -55
- package/dist/coValueCore/SessionMap.d.ts.map +0 -1
- package/dist/coValueCore/SessionMap.js +0 -206
- package/dist/coValueCore/SessionMap.js.map +0 -1
- package/dist/tests/coreWasm.test.d.ts +0 -2
- package/dist/tests/coreWasm.test.d.ts.map +0 -1
- package/dist/tests/coreWasm.test.js +0 -203
- package/dist/tests/coreWasm.test.js.map +0 -1
- package/src/coValueCore/SessionMap.ts +0 -394
- package/src/tests/coreWasm.test.ts +0 -452
package/src/coValues/group.ts
CHANGED
|
@@ -12,6 +12,9 @@ import type {
|
|
|
12
12
|
KeyID,
|
|
13
13
|
KeySecret,
|
|
14
14
|
Sealed,
|
|
15
|
+
SealedForGroup,
|
|
16
|
+
SealerID,
|
|
17
|
+
SealerSecret,
|
|
15
18
|
} from "../crypto/crypto.js";
|
|
16
19
|
import {
|
|
17
20
|
AgentID,
|
|
@@ -21,7 +24,7 @@ import {
|
|
|
21
24
|
isAgentID,
|
|
22
25
|
isParentGroupReference,
|
|
23
26
|
} from "../ids.js";
|
|
24
|
-
import { JsonObject } from "../jsonValue.js";
|
|
27
|
+
import { JsonObject, JsonValue } from "../jsonValue.js";
|
|
25
28
|
import { logger } from "../logger.js";
|
|
26
29
|
import {
|
|
27
30
|
AccountRole,
|
|
@@ -40,11 +43,52 @@ import {
|
|
|
40
43
|
import { RawCoList } from "./coList.js";
|
|
41
44
|
import { RawCoMap } from "./coMap.js";
|
|
42
45
|
import { RawCoPlainText } from "./coPlainText.js";
|
|
43
|
-
import { RawBinaryCoStream
|
|
46
|
+
import { RawBinaryCoStream } from "./binaryCoStream.js";
|
|
47
|
+
import { RawCoStream } from "./coStream.js";
|
|
44
48
|
|
|
45
49
|
export const EVERYONE = "everyone" as const;
|
|
46
50
|
export type Everyone = "everyone";
|
|
47
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Format a composite groupSealer value that includes the readKeyID.
|
|
54
|
+
* New format: "readKeyID@sealerID" - explicitly associates the sealer with
|
|
55
|
+
* the readKey it was derived from. This prevents inconsistency when different
|
|
56
|
+
* admins concurrently rotate keys and migrate the groupSealer.
|
|
57
|
+
*
|
|
58
|
+
* @internal
|
|
59
|
+
*/
|
|
60
|
+
export function formatGroupSealerValue(readKeyID: KeyID, sealerID: SealerID) {
|
|
61
|
+
return `${readKeyID}@${sealerID}` as const;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Extract the SealerID from a groupSealer field value.
|
|
66
|
+
* Handles both new format ("readKeyID@sealerID") and legacy format ("sealer_z...").
|
|
67
|
+
*
|
|
68
|
+
* @internal
|
|
69
|
+
*/
|
|
70
|
+
function extractSealerID(groupSealerValue: string): SealerID {
|
|
71
|
+
const idx = groupSealerValue.indexOf("@");
|
|
72
|
+
if (idx > 0) {
|
|
73
|
+
return groupSealerValue.substring(idx + 1) as SealerID;
|
|
74
|
+
}
|
|
75
|
+
return groupSealerValue as SealerID;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Extract the readKeyID from a groupSealer field value.
|
|
80
|
+
* Returns undefined for legacy format values that don't include the readKeyID.
|
|
81
|
+
*
|
|
82
|
+
* @internal
|
|
83
|
+
*/
|
|
84
|
+
function extractReadKeyID(groupSealerValue: string): KeyID | undefined {
|
|
85
|
+
const idx = groupSealerValue.indexOf("@");
|
|
86
|
+
if (idx > 0) {
|
|
87
|
+
return groupSealerValue.substring(0, idx) as KeyID;
|
|
88
|
+
}
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
|
|
48
92
|
export type ParentGroupReferenceRole =
|
|
49
93
|
| "revoked"
|
|
50
94
|
| "extend"
|
|
@@ -59,6 +103,9 @@ export type GroupShape = {
|
|
|
59
103
|
[key: RawAccountID | AgentID]: Role;
|
|
60
104
|
[EVERYONE]?: Role;
|
|
61
105
|
readKey?: KeyID;
|
|
106
|
+
// Group-level asymmetric encryption key (public portion only)
|
|
107
|
+
// Private key is derived from readKey, not stored
|
|
108
|
+
groupSealer?: `${KeyID}@${SealerID}`;
|
|
62
109
|
[writeKeyFor: `writeKeyFor_${RawAccountID | AgentID}`]: KeyID;
|
|
63
110
|
[revelationFor: `${KeyID}_for_${RawAccountID | AgentID}`]: Sealed<KeySecret>;
|
|
64
111
|
[revelationFor: `${KeyID}_for_${Everyone}`]: KeySecret;
|
|
@@ -66,6 +113,9 @@ export type GroupShape = {
|
|
|
66
113
|
KeySecret,
|
|
67
114
|
{ encryptedID: KeyID; encryptingID: KeyID }
|
|
68
115
|
>;
|
|
116
|
+
// Key revelations encrypted to group sealer (from non-members extending child groups)
|
|
117
|
+
// Using _sealedFor_ prefix to distinguish from _for_ patterns used for member/key revelations
|
|
118
|
+
[keyForSealer: `${KeyID}_sealedFor_${SealerID}`]: SealedForGroup<KeySecret>;
|
|
69
119
|
[parent: ParentGroupReference]: ParentGroupReferenceRole;
|
|
70
120
|
[child: ChildGroupReference]: "revoked" | "extend";
|
|
71
121
|
};
|
|
@@ -123,6 +173,45 @@ function healMissingKeyForEveryone(group: RawGroup) {
|
|
|
123
173
|
}
|
|
124
174
|
}
|
|
125
175
|
|
|
176
|
+
/**
|
|
177
|
+
* Backfill the groupSealer field for groups created before the feature was introduced.
|
|
178
|
+
* Since the groupSealer is derived deterministically from the readKey, parallel migrations
|
|
179
|
+
* from different accounts will always produce the same value.
|
|
180
|
+
*
|
|
181
|
+
* Only admins/managers can set the groupSealer field.
|
|
182
|
+
*/
|
|
183
|
+
function healMissingGroupSealer(group: RawGroup) {
|
|
184
|
+
if (group.get("groupSealer")) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Check direct membership only (not inherited roles via parent groups)
|
|
189
|
+
// to avoid accessing parentGroupsChanges which may not be initialized during early construction
|
|
190
|
+
const currentAccountOrAgent = group.core.node.getCurrentAccountOrAgentID();
|
|
191
|
+
const directRole = group.get(currentAccountOrAgent);
|
|
192
|
+
if (directRole !== "admin" && directRole !== "manager") {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const readKeyId = group.get("readKey");
|
|
197
|
+
if (!readKeyId) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const readKeySecret = group.getReadKey(readKeyId);
|
|
202
|
+
if (!readKeySecret) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const groupSealer =
|
|
207
|
+
group.core.node.crypto.groupSealerFromReadKey(readKeySecret);
|
|
208
|
+
group.set(
|
|
209
|
+
"groupSealer",
|
|
210
|
+
formatGroupSealerValue(readKeyId, groupSealer.publicKey),
|
|
211
|
+
"trusting",
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
126
215
|
function needsKeyRotation(group: RawGroup) {
|
|
127
216
|
const myRole = group.myRole();
|
|
128
217
|
|
|
@@ -262,7 +351,6 @@ export class RawGroup<
|
|
|
262
351
|
if (!this.keyRevelations) {
|
|
263
352
|
this.keyRevelations = new Map();
|
|
264
353
|
}
|
|
265
|
-
|
|
266
354
|
// Build caches incrementally
|
|
267
355
|
for (const changeValue of transaction.changes) {
|
|
268
356
|
const change = changeValue as {
|
|
@@ -324,6 +412,7 @@ export class RawGroup<
|
|
|
324
412
|
const runMigrations = () => {
|
|
325
413
|
// rotateReadKeyIfNeeded(this);
|
|
326
414
|
healMissingKeyForEveryone(this);
|
|
415
|
+
healMissingGroupSealer(this);
|
|
327
416
|
};
|
|
328
417
|
|
|
329
418
|
// We need the group and their parents to be completely downloaded to correctly handle the migrations
|
|
@@ -337,6 +426,13 @@ export class RawGroup<
|
|
|
337
426
|
}
|
|
338
427
|
}
|
|
339
428
|
|
|
429
|
+
/**
|
|
430
|
+
* Optional display name set at group creation. Immutable; stored in plaintext in header meta.
|
|
431
|
+
*/
|
|
432
|
+
get name(): string | undefined {
|
|
433
|
+
return (this.headerMeta as { name?: string } | null)?.name;
|
|
434
|
+
}
|
|
435
|
+
|
|
340
436
|
/**
|
|
341
437
|
* Returns the current role of a given account.
|
|
342
438
|
*
|
|
@@ -885,6 +981,69 @@ export class RawGroup<
|
|
|
885
981
|
}
|
|
886
982
|
}
|
|
887
983
|
}
|
|
984
|
+
|
|
985
|
+
// Try to find revelation via parent group sealer (anonymous box)
|
|
986
|
+
const parentContent = expectGroup(parentGroup.getCurrentContent());
|
|
987
|
+
const secret = this.tryDecryptWithGroupSealer(keyID, parentContent);
|
|
988
|
+
if (secret) {
|
|
989
|
+
return secret;
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
return undefined;
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
/**
|
|
997
|
+
* Try to decrypt a key that was revealed via a parent group's sealer.
|
|
998
|
+
* Walks the parent group's groupSealer history backwards (newest first)
|
|
999
|
+
* and tries to unseal the key revelation for each historical sealer value.
|
|
1000
|
+
*
|
|
1001
|
+
* New format groupSealer values embed the readKeyID directly (e.g., "key_z..._sealer_z...")
|
|
1002
|
+
* so we can deterministically find the correct readKey without time-based correlation.
|
|
1003
|
+
* Legacy format values (just "sealer_z...") fall back to time-based readKey lookup.
|
|
1004
|
+
*/
|
|
1005
|
+
private tryDecryptWithGroupSealer(
|
|
1006
|
+
keyID: KeyID,
|
|
1007
|
+
parentGroup: RawGroup,
|
|
1008
|
+
): KeySecret | undefined {
|
|
1009
|
+
const sealerEntries = parentGroup.ops["groupSealer"];
|
|
1010
|
+
if (!sealerEntries) return undefined;
|
|
1011
|
+
|
|
1012
|
+
// Iterate backwards (newest sealer first) to try the most recent one first
|
|
1013
|
+
for (let i = sealerEntries.length - 1; i >= 0; i--) {
|
|
1014
|
+
const sealerEntry = sealerEntries[i]!;
|
|
1015
|
+
if (sealerEntry.change.op !== "set") continue;
|
|
1016
|
+
const groupSealerValue = sealerEntry.change.value as string | undefined;
|
|
1017
|
+
if (!groupSealerValue) continue;
|
|
1018
|
+
|
|
1019
|
+
// Extract the SealerID (handles both new composite and legacy formats)
|
|
1020
|
+
const sealerID = extractSealerID(groupSealerValue);
|
|
1021
|
+
|
|
1022
|
+
const sealedKeyEdit = this.lastEditAt(`${keyID}_sealedFor_${sealerID}`);
|
|
1023
|
+
if (!sealedKeyEdit?.value) continue;
|
|
1024
|
+
|
|
1025
|
+
// Try to get the readKeyID directly from the composite value (new format)
|
|
1026
|
+
const readKeyID = extractReadKeyID(groupSealerValue);
|
|
1027
|
+
if (!readKeyID) continue;
|
|
1028
|
+
|
|
1029
|
+
const readKeySecret = parentGroup.getReadKey(readKeyID);
|
|
1030
|
+
if (!readKeySecret) continue;
|
|
1031
|
+
|
|
1032
|
+
const { secret: sealerSecret } =
|
|
1033
|
+
this.crypto.groupSealerFromReadKey(readKeySecret);
|
|
1034
|
+
|
|
1035
|
+
const secret = this.crypto.unsealForGroup(
|
|
1036
|
+
sealedKeyEdit.value as SealedForGroup<KeySecret>,
|
|
1037
|
+
sealerSecret,
|
|
1038
|
+
{
|
|
1039
|
+
in: this.id,
|
|
1040
|
+
tx: sealedKeyEdit.tx,
|
|
1041
|
+
},
|
|
1042
|
+
);
|
|
1043
|
+
|
|
1044
|
+
if (secret) {
|
|
1045
|
+
return secret;
|
|
1046
|
+
}
|
|
888
1047
|
}
|
|
889
1048
|
|
|
890
1049
|
return undefined;
|
|
@@ -1032,6 +1191,18 @@ export class RawGroup<
|
|
|
1032
1191
|
|
|
1033
1192
|
this.set("readKey", newReadKey.id, "trusting");
|
|
1034
1193
|
|
|
1194
|
+
// Update the group sealer (derived deterministically from the new read key)
|
|
1195
|
+
// Store composite value with readKeyID to prevent race conditions between
|
|
1196
|
+
// concurrent key rotations and groupSealer migrations
|
|
1197
|
+
const newGroupSealer = this.crypto.groupSealerFromReadKey(
|
|
1198
|
+
newReadKey.secret,
|
|
1199
|
+
);
|
|
1200
|
+
this.set(
|
|
1201
|
+
"groupSealer",
|
|
1202
|
+
formatGroupSealerValue(newReadKey.id, newGroupSealer.publicKey),
|
|
1203
|
+
"trusting",
|
|
1204
|
+
);
|
|
1205
|
+
|
|
1035
1206
|
/**
|
|
1036
1207
|
* The new read key needs to be revealed to the parent groups
|
|
1037
1208
|
*
|
|
@@ -1098,6 +1269,26 @@ export class RawGroup<
|
|
|
1098
1269
|
};
|
|
1099
1270
|
}
|
|
1100
1271
|
|
|
1272
|
+
/**
|
|
1273
|
+
* Get the group sealer secret by deriving it from the associated read key.
|
|
1274
|
+
* Uses the readKeyID embedded in the composite groupSealer value (new format),
|
|
1275
|
+
* or falls back to the current read key (legacy format).
|
|
1276
|
+
* Returns undefined if we don't have access to the read key.
|
|
1277
|
+
*/
|
|
1278
|
+
getGroupSealerSecret(): SealerSecret | undefined {
|
|
1279
|
+
const groupSealerValue = this.get("groupSealer");
|
|
1280
|
+
if (!groupSealerValue) return undefined;
|
|
1281
|
+
|
|
1282
|
+
const readKeyID = extractReadKeyID(groupSealerValue as string);
|
|
1283
|
+
const readKeySecret = readKeyID
|
|
1284
|
+
? this.getReadKey(readKeyID)
|
|
1285
|
+
: this.getCurrentReadKey().secret;
|
|
1286
|
+
|
|
1287
|
+
if (!readKeySecret) return undefined;
|
|
1288
|
+
|
|
1289
|
+
return this.crypto.groupSealerFromReadKey(readKeySecret).secret;
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1101
1292
|
extend(
|
|
1102
1293
|
parent: RawGroup,
|
|
1103
1294
|
role: "reader" | "writer" | "manager" | "admin" | "inherit" = "inherit",
|
|
@@ -1136,17 +1327,50 @@ export class RawGroup<
|
|
|
1136
1327
|
readKeySecret: KeySecret,
|
|
1137
1328
|
{ revealAllWriteOnlyKeys }: { revealAllWriteOnlyKeys: boolean },
|
|
1138
1329
|
) {
|
|
1139
|
-
|
|
1330
|
+
const parentGroupSealerValue = parent.get("groupSealer");
|
|
1140
1331
|
|
|
1332
|
+
// If we're not a member of the parent group, we need to use an alternative mechanism
|
|
1141
1333
|
if (!isAccountRole(parent.myRole())) {
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1334
|
+
if (parentGroupSealerValue) {
|
|
1335
|
+
// Extract the pure SealerID from the composite value (or legacy format)
|
|
1336
|
+
const parentSealerID = extractSealerID(
|
|
1337
|
+
parentGroupSealerValue as string,
|
|
1338
|
+
);
|
|
1339
|
+
|
|
1340
|
+
// NEW PATH: Use group sealer (anonymous box) instead of writeOnly key
|
|
1341
|
+
this.storeKeyRevelationForGroupSealer(
|
|
1342
|
+
parentSealerID,
|
|
1343
|
+
readKeyId,
|
|
1344
|
+
readKeySecret,
|
|
1345
|
+
);
|
|
1346
|
+
|
|
1347
|
+
// Also reveal all writeOnly keys if requested
|
|
1348
|
+
if (revealAllWriteOnlyKeys) {
|
|
1349
|
+
for (const keyID of this.getWriteOnlyKeys()) {
|
|
1350
|
+
const secret = this.core.getReadKey(keyID);
|
|
1351
|
+
if (!secret) {
|
|
1352
|
+
logger.error("Can't find key " + keyID);
|
|
1353
|
+
continue;
|
|
1354
|
+
}
|
|
1355
|
+
this.storeKeyRevelationForGroupSealer(
|
|
1356
|
+
parentSealerID,
|
|
1357
|
+
keyID,
|
|
1358
|
+
secret,
|
|
1359
|
+
);
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
return;
|
|
1363
|
+
} else {
|
|
1364
|
+
// LEGACY FALLBACK: Create a writeOnly key in the parent group
|
|
1365
|
+
parent.internalCreateWriteOnlyKeyForMember(
|
|
1366
|
+
this.core.node.getCurrentAgent().id,
|
|
1367
|
+
this.core.node.getCurrentAgent().currentAgentID(),
|
|
1368
|
+
);
|
|
1369
|
+
}
|
|
1147
1370
|
}
|
|
1148
1371
|
|
|
1149
|
-
|
|
1372
|
+
// Standard path: we have access to the parent's read key
|
|
1373
|
+
const { id: parentReadKeyID, secret: parentReadKeySecret } =
|
|
1150
1374
|
parent.getCurrentReadKey();
|
|
1151
1375
|
|
|
1152
1376
|
if (!parentReadKeySecret) {
|
|
@@ -1162,11 +1386,6 @@ export class RawGroup<
|
|
|
1162
1386
|
|
|
1163
1387
|
if (revealAllWriteOnlyKeys) {
|
|
1164
1388
|
for (const keyID of this.getWriteOnlyKeys()) {
|
|
1165
|
-
// If there's a new writeOnly key, it's already been revealed
|
|
1166
|
-
if (keyID === writeOnlyKeyID) {
|
|
1167
|
-
continue;
|
|
1168
|
-
}
|
|
1169
|
-
|
|
1170
1389
|
const secret = this.core.getReadKey(keyID);
|
|
1171
1390
|
|
|
1172
1391
|
if (!secret) {
|
|
@@ -1184,6 +1403,29 @@ export class RawGroup<
|
|
|
1184
1403
|
}
|
|
1185
1404
|
}
|
|
1186
1405
|
|
|
1406
|
+
/**
|
|
1407
|
+
* Store a key revelation encrypted to a parent group's sealer (anonymous box).
|
|
1408
|
+
* Used when extending a child group to a parent group we don't have access to.
|
|
1409
|
+
*/
|
|
1410
|
+
private storeKeyRevelationForGroupSealer(
|
|
1411
|
+
groupSealer: SealerID,
|
|
1412
|
+
childKeyID: KeyID,
|
|
1413
|
+
childKeySecret: KeySecret,
|
|
1414
|
+
) {
|
|
1415
|
+
this.set(
|
|
1416
|
+
`${childKeyID}_sealedFor_${groupSealer}`,
|
|
1417
|
+
this.crypto.sealForGroup({
|
|
1418
|
+
message: childKeySecret,
|
|
1419
|
+
to: groupSealer,
|
|
1420
|
+
nOnceMaterial: {
|
|
1421
|
+
in: this.id,
|
|
1422
|
+
tx: this.core.nextTransactionID(),
|
|
1423
|
+
},
|
|
1424
|
+
}),
|
|
1425
|
+
"trusting",
|
|
1426
|
+
);
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1187
1429
|
revokeExtend(parent: RawGroup) {
|
|
1188
1430
|
if (this.myRole() !== "admin") {
|
|
1189
1431
|
throw new Error(
|
|
@@ -1268,6 +1510,7 @@ export class RawGroup<
|
|
|
1268
1510
|
meta?: M["headerMeta"],
|
|
1269
1511
|
initPrivacy: "trusting" | "private" = "private",
|
|
1270
1512
|
uniqueness: CoValueUniqueness = this.crypto.createdNowUnique(),
|
|
1513
|
+
initMeta?: JsonObject,
|
|
1271
1514
|
): M {
|
|
1272
1515
|
const map = this.core.node
|
|
1273
1516
|
.createCoValue({
|
|
@@ -1285,10 +1528,10 @@ export class RawGroup<
|
|
|
1285
1528
|
.getCurrentContent() as M;
|
|
1286
1529
|
|
|
1287
1530
|
if (init) {
|
|
1288
|
-
map.assign(init, initPrivacy);
|
|
1531
|
+
map.assign(init, initPrivacy, initMeta);
|
|
1289
1532
|
} else if (!uniqueness.createdAt) {
|
|
1290
1533
|
// If the createdAt is not set, we need to make a trusting transaction to set the createdAt
|
|
1291
|
-
map.core.makeTransaction([], "trusting");
|
|
1534
|
+
map.core.makeTransaction([], "trusting", initMeta);
|
|
1292
1535
|
}
|
|
1293
1536
|
|
|
1294
1537
|
return map;
|
|
@@ -1305,6 +1548,7 @@ export class RawGroup<
|
|
|
1305
1548
|
meta?: L["headerMeta"],
|
|
1306
1549
|
initPrivacy: "trusting" | "private" = "private",
|
|
1307
1550
|
uniqueness: CoValueUniqueness = this.crypto.createdNowUnique(),
|
|
1551
|
+
initMeta?: JsonObject,
|
|
1308
1552
|
): L {
|
|
1309
1553
|
const list = this.core.node
|
|
1310
1554
|
.createCoValue({
|
|
@@ -1322,10 +1566,10 @@ export class RawGroup<
|
|
|
1322
1566
|
.getCurrentContent() as L;
|
|
1323
1567
|
|
|
1324
1568
|
if (init?.length) {
|
|
1325
|
-
list.appendItems(init, undefined, initPrivacy);
|
|
1569
|
+
list.appendItems(init, undefined, initPrivacy, initMeta);
|
|
1326
1570
|
} else if (!uniqueness.createdAt) {
|
|
1327
1571
|
// If the createdAt is not set, we need to make a trusting transaction to set the createdAt
|
|
1328
|
-
list.core.makeTransaction([], "trusting");
|
|
1572
|
+
list.core.makeTransaction([], "trusting", initMeta);
|
|
1329
1573
|
}
|
|
1330
1574
|
|
|
1331
1575
|
return list;
|
|
@@ -1363,8 +1607,11 @@ export class RawGroup<
|
|
|
1363
1607
|
|
|
1364
1608
|
/** @category 3. Value creation */
|
|
1365
1609
|
createStream<C extends RawCoStream>(
|
|
1610
|
+
init?: JsonValue[],
|
|
1611
|
+
initPrivacy: "trusting" | "private" = "private",
|
|
1366
1612
|
meta?: C["headerMeta"],
|
|
1367
1613
|
uniqueness: CoValueUniqueness = this.crypto.createdNowUnique(),
|
|
1614
|
+
initMeta?: JsonObject,
|
|
1368
1615
|
): C {
|
|
1369
1616
|
const stream = this.core.node
|
|
1370
1617
|
.createCoValue({
|
|
@@ -1381,9 +1628,11 @@ export class RawGroup<
|
|
|
1381
1628
|
})
|
|
1382
1629
|
.getCurrentContent() as C;
|
|
1383
1630
|
|
|
1384
|
-
if (
|
|
1631
|
+
if (init?.length) {
|
|
1632
|
+
stream.core.makeTransaction(init, initPrivacy, initMeta);
|
|
1633
|
+
} else if (!uniqueness.createdAt) {
|
|
1385
1634
|
// If the createdAt is not set, we need to make a trusting transaction to set the createdAt
|
|
1386
|
-
stream.core.makeTransaction([], "trusting");
|
|
1635
|
+
stream.core.makeTransaction([], "trusting", initMeta);
|
|
1387
1636
|
}
|
|
1388
1637
|
|
|
1389
1638
|
return stream;
|
package/src/coreToCoValue.ts
CHANGED
|
@@ -4,7 +4,8 @@ import { RawAccount } from "./coValues/account.js";
|
|
|
4
4
|
import { RawCoList } from "./coValues/coList.js";
|
|
5
5
|
import { RawCoMap } from "./coValues/coMap.js";
|
|
6
6
|
import { RawCoPlainText } from "./coValues/coPlainText.js";
|
|
7
|
-
import { RawBinaryCoStream
|
|
7
|
+
import { RawBinaryCoStream } from "./coValues/binaryCoStream.js";
|
|
8
|
+
import { RawCoStream } from "./coValues/coStream.js";
|
|
8
9
|
import { RawGroup } from "./coValues/group.js";
|
|
9
10
|
|
|
10
11
|
export function coreToCoValue(
|