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.
Files changed (209) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +26 -0
  3. package/dist/SyncStateManager.d.ts.map +1 -1
  4. package/dist/SyncStateManager.js +0 -2
  5. package/dist/SyncStateManager.js.map +1 -1
  6. package/dist/base64url.d.ts +15 -0
  7. package/dist/base64url.d.ts.map +1 -1
  8. package/dist/base64url.js +101 -5
  9. package/dist/base64url.js.map +1 -1
  10. package/dist/base64url.test.js +76 -1
  11. package/dist/base64url.test.js.map +1 -1
  12. package/dist/coValue.d.ts +2 -1
  13. package/dist/coValue.d.ts.map +1 -1
  14. package/dist/coValue.js.map +1 -1
  15. package/dist/coValueCore/coValueCore.d.ts +9 -11
  16. package/dist/coValueCore/coValueCore.d.ts.map +1 -1
  17. package/dist/coValueCore/coValueCore.js +92 -65
  18. package/dist/coValueCore/coValueCore.js.map +1 -1
  19. package/dist/coValueCore/verifiedState.d.ts +38 -7
  20. package/dist/coValueCore/verifiedState.d.ts.map +1 -1
  21. package/dist/coValueCore/verifiedState.js +226 -30
  22. package/dist/coValueCore/verifiedState.js.map +1 -1
  23. package/dist/coValues/binaryCoStream.d.ts +63 -0
  24. package/dist/coValues/binaryCoStream.d.ts.map +1 -0
  25. package/dist/coValues/binaryCoStream.js +125 -0
  26. package/dist/coValues/binaryCoStream.js.map +1 -0
  27. package/dist/coValues/coList.d.ts +3 -1
  28. package/dist/coValues/coList.d.ts.map +1 -1
  29. package/dist/coValues/coList.js +15 -6
  30. package/dist/coValues/coList.js.map +1 -1
  31. package/dist/coValues/coMap.d.ts +1 -1
  32. package/dist/coValues/coMap.d.ts.map +1 -1
  33. package/dist/coValues/coMap.js +2 -2
  34. package/dist/coValues/coMap.js.map +1 -1
  35. package/dist/coValues/coStream.d.ts +0 -38
  36. package/dist/coValues/coStream.d.ts.map +1 -1
  37. package/dist/coValues/coStream.js +0 -86
  38. package/dist/coValues/coStream.js.map +1 -1
  39. package/dist/coValues/group.d.ts +44 -6
  40. package/dist/coValues/group.d.ts.map +1 -1
  41. package/dist/coValues/group.js +198 -17
  42. package/dist/coValues/group.js.map +1 -1
  43. package/dist/coreToCoValue.d.ts +2 -1
  44. package/dist/coreToCoValue.d.ts.map +1 -1
  45. package/dist/coreToCoValue.js +2 -1
  46. package/dist/coreToCoValue.js.map +1 -1
  47. package/dist/crypto/NapiCrypto.d.ts +18 -24
  48. package/dist/crypto/NapiCrypto.d.ts.map +1 -1
  49. package/dist/crypto/NapiCrypto.js +98 -60
  50. package/dist/crypto/NapiCrypto.js.map +1 -1
  51. package/dist/crypto/RNCrypto.d.ts +16 -3
  52. package/dist/crypto/RNCrypto.d.ts.map +1 -1
  53. package/dist/crypto/RNCrypto.js +117 -54
  54. package/dist/crypto/RNCrypto.js.map +1 -1
  55. package/dist/crypto/WasmCrypto.d.ts +18 -24
  56. package/dist/crypto/WasmCrypto.d.ts.map +1 -1
  57. package/dist/crypto/WasmCrypto.js +100 -61
  58. package/dist/crypto/WasmCrypto.js.map +1 -1
  59. package/dist/crypto/crypto.d.ts +55 -19
  60. package/dist/crypto/crypto.d.ts.map +1 -1
  61. package/dist/crypto/crypto.js +14 -3
  62. package/dist/crypto/crypto.js.map +1 -1
  63. package/dist/exports.d.ts +7 -3
  64. package/dist/exports.d.ts.map +1 -1
  65. package/dist/exports.js +4 -2
  66. package/dist/exports.js.map +1 -1
  67. package/dist/localNode.d.ts +3 -1
  68. package/dist/localNode.d.ts.map +1 -1
  69. package/dist/localNode.js +10 -3
  70. package/dist/localNode.js.map +1 -1
  71. package/dist/media.d.ts +1 -1
  72. package/dist/media.d.ts.map +1 -1
  73. package/dist/permissions.d.ts +2 -1
  74. package/dist/permissions.d.ts.map +1 -1
  75. package/dist/permissions.js +19 -3
  76. package/dist/permissions.js.map +1 -1
  77. package/dist/storage/sqliteAsync/client.d.ts +24 -12
  78. package/dist/storage/sqliteAsync/client.d.ts.map +1 -1
  79. package/dist/storage/sqliteAsync/client.js +70 -58
  80. package/dist/storage/sqliteAsync/client.js.map +1 -1
  81. package/dist/storage/sqliteAsync/types.d.ts +1 -1
  82. package/dist/storage/sqliteAsync/types.d.ts.map +1 -1
  83. package/dist/storage/types.d.ts +1 -0
  84. package/dist/storage/types.d.ts.map +1 -1
  85. package/dist/sync.d.ts.map +1 -1
  86. package/dist/sync.js +7 -1
  87. package/dist/sync.js.map +1 -1
  88. package/dist/tests/CojsonMessageChannel.test.js +2 -2
  89. package/dist/tests/SQLiteClientAsync.test.d.ts +2 -0
  90. package/dist/tests/SQLiteClientAsync.test.d.ts.map +1 -0
  91. package/dist/tests/SQLiteClientAsync.test.js +64 -0
  92. package/dist/tests/SQLiteClientAsync.test.js.map +1 -0
  93. package/dist/tests/StorageApiAsync.test.js +2 -8
  94. package/dist/tests/StorageApiAsync.test.js.map +1 -1
  95. package/dist/tests/SyncStateManager.test.js +2 -2
  96. package/dist/tests/WasmCrypto.test.js +1 -15
  97. package/dist/tests/WasmCrypto.test.js.map +1 -1
  98. package/dist/tests/coList.test.js +24 -5
  99. package/dist/tests/coList.test.js.map +1 -1
  100. package/dist/tests/coStream.test.js +4 -3
  101. package/dist/tests/coStream.test.js.map +1 -1
  102. package/dist/tests/coValueCore.initTransaction.test.d.ts +2 -0
  103. package/dist/tests/coValueCore.initTransaction.test.d.ts.map +1 -0
  104. package/dist/tests/coValueCore.initTransaction.test.js +438 -0
  105. package/dist/tests/coValueCore.initTransaction.test.js.map +1 -0
  106. package/dist/tests/coValueCore.test.js +11 -19
  107. package/dist/tests/coValueCore.test.js.map +1 -1
  108. package/dist/tests/crypto.test.js +83 -0
  109. package/dist/tests/crypto.test.js.map +1 -1
  110. package/dist/tests/deleteCoValue.test.js +5 -5
  111. package/dist/tests/deleteCoValue.test.js.map +1 -1
  112. package/dist/tests/group.inheritance.test.js +11 -0
  113. package/dist/tests/group.inheritance.test.js.map +1 -1
  114. package/dist/tests/group.test.js +24 -1
  115. package/dist/tests/group.test.js.map +1 -1
  116. package/dist/tests/groupSealer.test.d.ts +2 -0
  117. package/dist/tests/groupSealer.test.d.ts.map +1 -0
  118. package/dist/tests/groupSealer.test.js +913 -0
  119. package/dist/tests/groupSealer.test.js.map +1 -0
  120. package/dist/tests/setup.js +5 -0
  121. package/dist/tests/setup.js.map +1 -1
  122. package/dist/tests/sync.auth.test.js +10 -10
  123. package/dist/tests/sync.concurrentLoad.test.js +12 -12
  124. package/dist/tests/sync.deleted.test.js +8 -8
  125. package/dist/tests/sync.garbageCollection.test.js +10 -10
  126. package/dist/tests/sync.invite.test.js +12 -12
  127. package/dist/tests/sync.known.test.js +2 -2
  128. package/dist/tests/sync.load.test.js +107 -107
  129. package/dist/tests/sync.mesh.test.js +164 -46
  130. package/dist/tests/sync.mesh.test.js.map +1 -1
  131. package/dist/tests/sync.multipleServers.test.js +43 -43
  132. package/dist/tests/sync.peerReconciliation.test.js +29 -29
  133. package/dist/tests/sync.sharding.test.js +3 -3
  134. package/dist/tests/sync.storage.test.js +104 -104
  135. package/dist/tests/sync.storage.test.js.map +1 -1
  136. package/dist/tests/sync.storageAsync.test.js +56 -56
  137. package/dist/tests/sync.upload.test.js +22 -22
  138. package/dist/tests/testStorage.d.ts +2 -0
  139. package/dist/tests/testStorage.d.ts.map +1 -1
  140. package/dist/tests/testStorage.js +30 -6
  141. package/dist/tests/testStorage.js.map +1 -1
  142. package/dist/typeUtils/isCoValue.js +1 -1
  143. package/dist/typeUtils/isCoValue.js.map +1 -1
  144. package/package.json +4 -4
  145. package/src/SyncStateManager.ts +0 -2
  146. package/src/base64url.test.ts +89 -1
  147. package/src/base64url.ts +134 -6
  148. package/src/coValue.ts +2 -1
  149. package/src/coValueCore/coValueCore.ts +126 -84
  150. package/src/coValueCore/verifiedState.ts +335 -53
  151. package/src/coValues/binaryCoStream.ts +217 -0
  152. package/src/coValues/coList.ts +21 -8
  153. package/src/coValues/coMap.ts +3 -0
  154. package/src/coValues/coStream.ts +0 -170
  155. package/src/coValues/group.ts +270 -21
  156. package/src/coreToCoValue.ts +2 -1
  157. package/src/crypto/NapiCrypto.ts +198 -95
  158. package/src/crypto/RNCrypto.ts +229 -102
  159. package/src/crypto/WasmCrypto.ts +201 -95
  160. package/src/crypto/crypto.ts +118 -45
  161. package/src/exports.ts +11 -5
  162. package/src/localNode.ts +17 -1
  163. package/src/media.ts +1 -1
  164. package/src/permissions.ts +30 -7
  165. package/src/storage/sqliteAsync/client.ts +136 -115
  166. package/src/storage/sqliteAsync/types.ts +3 -1
  167. package/src/storage/types.ts +4 -0
  168. package/src/sync.ts +10 -1
  169. package/src/tests/CojsonMessageChannel.test.ts +2 -2
  170. package/src/tests/SQLiteClientAsync.test.ts +75 -0
  171. package/src/tests/StorageApiAsync.test.ts +4 -9
  172. package/src/tests/SyncStateManager.test.ts +2 -2
  173. package/src/tests/WasmCrypto.test.ts +1 -25
  174. package/src/tests/coList.test.ts +39 -5
  175. package/src/tests/coStream.test.ts +4 -5
  176. package/src/tests/coValueCore.initTransaction.test.ts +836 -0
  177. package/src/tests/coValueCore.test.ts +11 -22
  178. package/src/tests/crypto.test.ts +107 -0
  179. package/src/tests/deleteCoValue.test.ts +5 -5
  180. package/src/tests/group.inheritance.test.ts +16 -0
  181. package/src/tests/group.test.ts +29 -1
  182. package/src/tests/groupSealer.test.ts +1473 -0
  183. package/src/tests/setup.ts +6 -0
  184. package/src/tests/sync.auth.test.ts +10 -10
  185. package/src/tests/sync.concurrentLoad.test.ts +12 -12
  186. package/src/tests/sync.deleted.test.ts +8 -8
  187. package/src/tests/sync.garbageCollection.test.ts +10 -10
  188. package/src/tests/sync.invite.test.ts +12 -12
  189. package/src/tests/sync.known.test.ts +2 -2
  190. package/src/tests/sync.load.test.ts +107 -107
  191. package/src/tests/sync.mesh.test.ts +189 -46
  192. package/src/tests/sync.multipleServers.test.ts +43 -43
  193. package/src/tests/sync.peerReconciliation.test.ts +29 -29
  194. package/src/tests/sync.sharding.test.ts +3 -3
  195. package/src/tests/sync.storage.test.ts +104 -104
  196. package/src/tests/sync.storageAsync.test.ts +56 -56
  197. package/src/tests/sync.upload.test.ts +22 -22
  198. package/src/tests/testStorage.ts +39 -9
  199. package/src/typeUtils/isCoValue.ts +1 -1
  200. package/dist/coValueCore/SessionMap.d.ts +0 -55
  201. package/dist/coValueCore/SessionMap.d.ts.map +0 -1
  202. package/dist/coValueCore/SessionMap.js +0 -206
  203. package/dist/coValueCore/SessionMap.js.map +0 -1
  204. package/dist/tests/coreWasm.test.d.ts +0 -2
  205. package/dist/tests/coreWasm.test.d.ts.map +0 -1
  206. package/dist/tests/coreWasm.test.js +0 -203
  207. package/dist/tests/coreWasm.test.js.map +0 -1
  208. package/src/coValueCore/SessionMap.ts +0 -394
  209. package/src/tests/coreWasm.test.ts +0 -452
@@ -1,5 +1,5 @@
1
1
  import {
2
- SessionLog,
2
+ SessionMap as WasmSessionMap,
3
3
  initialize,
4
4
  initializeSync,
5
5
  Blake3Hasher,
@@ -12,35 +12,35 @@ import {
12
12
  newEd25519SigningKey,
13
13
  newX25519PrivateKey,
14
14
  seal,
15
+ sealForGroup,
16
+ shortHash,
15
17
  sign,
16
18
  unseal,
19
+ unsealForGroup,
17
20
  verify,
18
21
  } from "cojson-core-wasm";
19
22
  import { base64URLtoBytes, bytesToBase64url } from "../base64url.js";
20
- import { RawCoID, SessionID, TransactionID } from "../ids.js";
23
+ import { RawCoID, TransactionID } from "../ids.js";
24
+ import { Transaction } from "../coValueCore/verifiedState.js";
21
25
  import { Stringified, stableStringify } from "../jsonStringify.js";
22
- import { JsonObject, JsonValue } from "../jsonValue.js";
26
+ import { JsonValue } from "../jsonValue.js";
23
27
  import { logger } from "../logger.js";
24
28
  import {
25
29
  CryptoProvider,
26
30
  Encrypted,
27
- KeyID,
28
31
  KeySecret,
29
32
  Sealed,
33
+ SealedForGroup,
30
34
  SealerID,
31
35
  SealerSecret,
36
+ SessionMapImpl,
37
+ ShortHash,
32
38
  Signature,
33
39
  SignerID,
34
40
  SignerSecret,
35
41
  textDecoder,
36
42
  textEncoder,
37
43
  } from "./crypto.js";
38
- import { ControlledAccountOrAgent } from "../coValues/account.js";
39
- import {
40
- PrivateTransaction,
41
- Transaction,
42
- TrustingTransaction,
43
- } from "../coValueCore/verifiedState.js";
44
44
  import { isCloudflare, isEvalAllowed } from "../platformUtils.js";
45
45
 
46
46
  type Blake3State = Blake3Hasher;
@@ -117,6 +117,10 @@ export class WasmCrypto extends CryptoProvider<Blake3State> {
117
117
  return blake3HashOnceWithContext(data, context);
118
118
  }
119
119
 
120
+ shortHash(value: JsonValue): ShortHash {
121
+ return shortHash(stableStringify(value)) as ShortHash;
122
+ }
123
+
120
124
  newEd25519SigningKey(): Uint8Array {
121
125
  return newEd25519SigningKey();
122
126
  }
@@ -221,116 +225,218 @@ export class WasmCrypto extends CryptoProvider<Blake3State> {
221
225
  }
222
226
  }
223
227
 
224
- createSessionLog(coID: RawCoID, sessionID: SessionID, signerID?: SignerID) {
225
- return new SessionLogAdapter(new SessionLog(coID, sessionID, signerID));
228
+ sealForGroup<T extends JsonValue>({
229
+ message,
230
+ to,
231
+ nOnceMaterial,
232
+ }: {
233
+ message: T;
234
+ to: SealerID;
235
+ nOnceMaterial: { in: RawCoID; tx: TransactionID };
236
+ }): SealedForGroup<T> {
237
+ return `sealedForGroup_U${bytesToBase64url(
238
+ sealForGroup(
239
+ textEncoder.encode(stableStringify(message)),
240
+ to,
241
+ textEncoder.encode(stableStringify(nOnceMaterial)),
242
+ ),
243
+ )}` as SealedForGroup<T>;
244
+ }
245
+
246
+ unsealForGroup<T extends JsonValue>(
247
+ sealed: SealedForGroup<T>,
248
+ groupSealerSecret: SealerSecret,
249
+ nOnceMaterial: { in: RawCoID; tx: TransactionID },
250
+ ): T | undefined {
251
+ try {
252
+ const plaintext = textDecoder.decode(
253
+ unsealForGroup(
254
+ base64URLtoBytes(sealed.substring("sealedForGroup_U".length)),
255
+ groupSealerSecret,
256
+ textEncoder.encode(stableStringify(nOnceMaterial)),
257
+ ),
258
+ );
259
+ return JSON.parse(plaintext) as T;
260
+ } catch (e) {
261
+ logger.error("Failed to decrypt/parse sealed for group message", {
262
+ err: e,
263
+ });
264
+ return undefined;
265
+ }
266
+ }
267
+
268
+ createSessionMap(
269
+ coID: RawCoID,
270
+ headerJson: string,
271
+ maxTxSize?: number,
272
+ skipVerify?: boolean,
273
+ ): SessionMapImpl {
274
+ return new SessionMapAdapter(
275
+ new WasmSessionMap(coID, headerJson, maxTxSize, skipVerify),
276
+ );
226
277
  }
227
278
  }
228
279
 
229
- class SessionLogAdapter {
230
- constructor(private readonly sessionLog: SessionLog) {}
280
+ /**
281
+ * Adapter wrapping WasmSessionMap to implement SessionMapImpl interface
282
+ */
283
+ class SessionMapAdapter implements SessionMapImpl {
284
+ constructor(private readonly sessionMap: WasmSessionMap) {}
285
+
286
+ // === Header ===
287
+ getHeader(): string {
288
+ return this.sessionMap.getHeader();
289
+ }
231
290
 
232
- tryAdd(
233
- transactions: Transaction[],
234
- newSignature: Signature,
291
+ // === Transaction Operations ===
292
+ addTransactions(
293
+ sessionId: string,
294
+ signerId: string | undefined,
295
+ transactionsJson: string,
296
+ signature: string,
235
297
  skipVerify: boolean,
236
298
  ): void {
237
- // Use direct calls instead of JSON.stringify for better performance
238
- for (const tx of transactions) {
239
- if (tx.privacy === "private") {
240
- this.sessionLog.addExistingPrivateTransaction(
241
- tx.encryptedChanges,
242
- tx.keyUsed,
243
- tx.madeAt,
244
- tx.meta,
245
- );
246
- } else {
247
- this.sessionLog.addExistingTrustingTransaction(
248
- tx.changes,
249
- tx.madeAt,
250
- tx.meta,
251
- );
252
- }
253
- }
254
- this.sessionLog.commitTransactions(newSignature, skipVerify);
299
+ this.sessionMap.addTransactions(
300
+ sessionId,
301
+ signerId,
302
+ transactionsJson,
303
+ signature,
304
+ skipVerify,
305
+ );
255
306
  }
256
307
 
257
- addNewPrivateTransaction(
258
- signerAgent: ControlledAccountOrAgent,
259
- changes: JsonValue[],
260
- keyID: KeyID,
261
- keySecret: KeySecret,
308
+ makeNewPrivateTransaction(
309
+ sessionId: string,
310
+ signerSecret: string,
311
+ changesJson: string,
312
+ keyId: string,
313
+ keySecret: string,
314
+ metaJson: string | undefined,
262
315
  madeAt: number,
263
- meta: JsonObject | undefined,
264
- ): { signature: Signature; transaction: PrivateTransaction } {
265
- const output = this.sessionLog.addNewPrivateTransaction(
266
- // We can avoid stableStringify because it will be encrypted.
267
- JSON.stringify(changes),
268
- signerAgent.currentSignerSecret(),
316
+ ): string {
317
+ return this.sessionMap.makeNewPrivateTransaction(
318
+ sessionId,
319
+ signerSecret,
320
+ changesJson,
321
+ keyId,
269
322
  keySecret,
270
- keyID,
323
+ metaJson,
271
324
  madeAt,
272
- // We can avoid stableStringify because it will be encrypted.
273
- meta ? JSON.stringify(meta) : undefined,
274
325
  );
275
- const parsedOutput = JSON.parse(output);
276
- const transaction: PrivateTransaction = {
277
- privacy: "private",
278
- madeAt,
279
- encryptedChanges: parsedOutput.encrypted_changes,
280
- keyUsed: keyID,
281
- meta: parsedOutput.meta,
282
- };
283
- return { signature: parsedOutput.signature, transaction };
284
326
  }
285
327
 
286
- addNewTrustingTransaction(
287
- signerAgent: ControlledAccountOrAgent,
288
- changes: JsonValue[],
328
+ makeNewTrustingTransaction(
329
+ sessionId: string,
330
+ signerSecret: string,
331
+ changesJson: string,
332
+ metaJson: string | undefined,
289
333
  madeAt: number,
290
- meta: JsonObject | undefined,
291
- ): { signature: Signature; transaction: TrustingTransaction } {
292
- // We can avoid stableStringify because the changes will be in a string format already.
293
- const stringifiedChanges = JSON.stringify(changes);
294
- // We can avoid stableStringify because the meta will be in a string format already.
295
- const stringifiedMeta = meta ? JSON.stringify(meta) : undefined;
296
- const output = this.sessionLog.addNewTrustingTransaction(
297
- stringifiedChanges,
298
- signerAgent.currentSignerSecret(),
334
+ ): string {
335
+ return this.sessionMap.makeNewTrustingTransaction(
336
+ sessionId,
337
+ signerSecret,
338
+ changesJson,
339
+ metaJson,
299
340
  madeAt,
300
- stringifiedMeta,
301
341
  );
302
- const transaction: TrustingTransaction = {
303
- privacy: "trusting",
304
- madeAt,
305
- changes: stringifiedChanges as Stringified<JsonValue[]>,
306
- meta: stringifiedMeta as Stringified<JsonObject> | undefined,
342
+ }
343
+
344
+ // === Session Queries ===
345
+ getSessionIds(): string[] {
346
+ return this.sessionMap.getSessionIds();
347
+ }
348
+
349
+ getTransactionCount(sessionId: string): number {
350
+ return this.sessionMap.getTransactionCount(sessionId);
351
+ }
352
+
353
+ getTransaction(sessionId: string, txIndex: number): Transaction | undefined {
354
+ const result = this.sessionMap.getTransaction(sessionId, txIndex);
355
+ if (!result) return undefined;
356
+ return JSON.parse(result) as Transaction;
357
+ }
358
+
359
+ getSessionTransactions(
360
+ sessionId: string,
361
+ fromIndex: number,
362
+ ): Transaction[] | undefined {
363
+ const result = this.sessionMap.getSessionTransactions(sessionId, fromIndex);
364
+ if (!result) return undefined;
365
+ return result.map((tx) => JSON.parse(tx) as Transaction);
366
+ }
367
+
368
+ getLastSignature(sessionId: string): string | undefined {
369
+ return this.sessionMap.getLastSignature(sessionId) ?? undefined;
370
+ }
371
+
372
+ getSignatureAfter(sessionId: string, txIndex: number): string | undefined {
373
+ return this.sessionMap.getSignatureAfter(sessionId, txIndex) ?? undefined;
374
+ }
375
+
376
+ getLastSignatureCheckpoint(sessionId: string): number | undefined {
377
+ return this.sessionMap.getLastSignatureCheckpoint(sessionId) ?? undefined;
378
+ }
379
+
380
+ // === Known State ===
381
+ getKnownState(): {
382
+ id: string;
383
+ header: boolean;
384
+ sessions: Record<string, number>;
385
+ } {
386
+ // WASM returns a native JS object via serde_wasm_bindgen
387
+ return this.sessionMap.getKnownState() as {
388
+ id: string;
389
+ header: boolean;
390
+ sessions: Record<string, number>;
307
391
  };
308
- return { signature: output as Signature, transaction };
309
392
  }
310
393
 
311
- decryptNextTransactionChangesJson(
312
- txIndex: number,
313
- keySecret: KeySecret,
314
- ): string {
315
- const output = this.sessionLog.decryptNextTransactionChangesJson(
316
- txIndex,
317
- keySecret,
318
- );
319
- return output;
394
+ getKnownStateWithStreaming():
395
+ | { id: string; header: boolean; sessions: Record<string, number> }
396
+ | undefined {
397
+ // WASM returns a native JS object via serde_wasm_bindgen, or undefined
398
+ const result = this.sessionMap.getKnownStateWithStreaming();
399
+ if (!result || result === undefined) return undefined;
400
+ return result as {
401
+ id: string;
402
+ header: boolean;
403
+ sessions: Record<string, number>;
404
+ };
320
405
  }
321
406
 
322
- decryptNextTransactionMetaJson(
323
- txIndex: number,
324
- keySecret: KeySecret,
325
- ): string | undefined {
326
- return this.sessionLog.decryptNextTransactionMetaJson(txIndex, keySecret);
407
+ setStreamingKnownState(streamingJson: string): void {
408
+ this.sessionMap.setStreamingKnownState(streamingJson);
409
+ }
410
+
411
+ // === Deletion ===
412
+ markAsDeleted(): void {
413
+ this.sessionMap.markAsDeleted();
327
414
  }
328
415
 
329
- free() {
330
- this.sessionLog.free();
416
+ isDeleted(): boolean {
417
+ return this.sessionMap.isDeleted();
331
418
  }
332
419
 
333
- clone(): SessionLogAdapter {
334
- return new SessionLogAdapter(this.sessionLog.clone());
420
+ // === Decryption ===
421
+ decryptTransaction(
422
+ sessionId: string,
423
+ txIndex: number,
424
+ keySecret: string,
425
+ ): string | undefined {
426
+ return (
427
+ this.sessionMap.decryptTransaction(sessionId, txIndex, keySecret) ??
428
+ undefined
429
+ );
430
+ }
431
+
432
+ decryptTransactionMeta(
433
+ sessionId: string,
434
+ txIndex: number,
435
+ keySecret: string,
436
+ ): string | undefined {
437
+ return (
438
+ this.sessionMap.decryptTransactionMeta(sessionId, txIndex, keySecret) ??
439
+ undefined
440
+ );
335
441
  }
336
442
  }
@@ -1,21 +1,16 @@
1
1
  import { base58 } from "@scure/base";
2
- import { ControlledAccountOrAgent, RawAccountID } from "../coValues/account.js";
2
+ import { RawAccountID } from "../coValues/account.js";
3
3
  import {
4
4
  AgentID,
5
5
  RawCoID,
6
6
  TransactionID,
7
- SessionID,
8
7
  ActiveSessionID,
9
8
  DeleteSessionID,
10
9
  } from "../ids.js";
11
10
  import { Stringified, parseJSON, stableStringify } from "../jsonStringify.js";
12
- import { JsonObject, JsonValue } from "../jsonValue.js";
11
+ import { JsonValue } from "../jsonValue.js";
13
12
  import { logger } from "../logger.js";
14
- import {
15
- PrivateTransaction,
16
- Transaction,
17
- TrustingTransaction,
18
- } from "../coValueCore/verifiedState.js";
13
+ import { Transaction } from "../coValueCore/verifiedState.js";
19
14
 
20
15
  function randomBytes(bytesLength = 32): Uint8Array {
21
16
  return crypto.getRandomValues(new Uint8Array(bytesLength));
@@ -28,6 +23,8 @@ export type Signature = `signature_z${string}`;
28
23
  export type SealerSecret = `sealerSecret_z${string}`;
29
24
  export type SealerID = `sealer_z${string}`;
30
25
  export type Sealed<T> = `sealed_U${string}` & { __type: T };
26
+ // Anonymous box - encrypted to a group's sealer without sender authentication
27
+ export type SealedForGroup<T> = `sealedForGroup_U${string}` & { __type: T };
31
28
 
32
29
  export type AgentSecret = `${SealerSecret}/${SignerSecret}`;
33
30
 
@@ -111,14 +108,7 @@ export abstract class CryptoProvider<Blake3State = any> {
111
108
  )}`;
112
109
  }
113
110
 
114
- shortHash(value: JsonValue): ShortHash {
115
- return `shortHash_z${base58.encode(
116
- this.blake3HashOnce(textEncoder.encode(stableStringify(value))).slice(
117
- 0,
118
- shortHashLength,
119
- ),
120
- )}`;
121
- }
111
+ abstract shortHash(value: JsonValue): ShortHash;
122
112
 
123
113
  abstract encrypt<T extends JsonValue, N extends JsonValue>(
124
114
  value: T,
@@ -217,6 +207,45 @@ export abstract class CryptoProvider<Blake3State = any> {
217
207
  nOnceMaterial: { in: RawCoID; tx: TransactionID },
218
208
  ): T | undefined;
219
209
 
210
+ // Derive group sealer deterministically from read key
211
+ // This ensures concurrent migrations by different admins produce the same result
212
+ groupSealerFromReadKey(readKeySecret: KeySecret): {
213
+ publicKey: SealerID;
214
+ secret: SealerSecret;
215
+ } {
216
+ const sealerBytes = this.blake3HashOnceWithContext(
217
+ textEncoder.encode(readKeySecret),
218
+ { context: textEncoder.encode("groupSealer") },
219
+ );
220
+ // Blake3 output must be exactly 32 bytes to match X25519 secret key length
221
+ if (sealerBytes.length !== 32) {
222
+ throw new Error(
223
+ `Blake3 output must be 32 bytes for X25519 key, got ${sealerBytes.length}`,
224
+ );
225
+ }
226
+ const secret: SealerSecret = `sealerSecret_z${base58.encode(sealerBytes)}`;
227
+ return {
228
+ secret,
229
+ publicKey: this.getSealerID(secret),
230
+ };
231
+ }
232
+
233
+ // Anonymous box - encrypt data to a group's sealer without sender authentication
234
+ // Uses ephemeral key pair, embeds ephemeral public key in output
235
+ abstract sealForGroup<T extends JsonValue>(args: {
236
+ message: T;
237
+ to: SealerID;
238
+ nOnceMaterial: { in: RawCoID; tx: TransactionID };
239
+ }): SealedForGroup<T>;
240
+
241
+ // Decrypt data sealed to a group
242
+ // Extracts ephemeral public key from sealed data, derives shared secret
243
+ abstract unsealForGroup<T extends JsonValue>(
244
+ sealed: SealedForGroup<T>,
245
+ groupSealerSecret: SealerSecret,
246
+ nOnceMaterial: { in: RawCoID; tx: TransactionID },
247
+ ): T | undefined;
248
+
220
249
  uniquenessForHeader(): `z${string}` {
221
250
  return `z${base58.encode(this.randomBytes(12))}`;
222
251
  }
@@ -262,11 +291,12 @@ export abstract class CryptoProvider<Blake3State = any> {
262
291
  return `${accountID}_session_d${randomPart}$`;
263
292
  }
264
293
 
265
- abstract createSessionLog(
294
+ abstract createSessionMap(
266
295
  coID: RawCoID,
267
- sessionID: SessionID,
268
- signerID?: SignerID,
269
- ): SessionLogImpl;
296
+ headerJson: string,
297
+ maxTxSize?: number,
298
+ skipVerify?: boolean,
299
+ ): SessionMapImpl;
270
300
  }
271
301
 
272
302
  export type Hash = `hash_z${string}`;
@@ -283,34 +313,77 @@ export type KeyID = `key_z${string}`;
283
313
 
284
314
  export const secretSeedLength = 32;
285
315
 
286
- export interface SessionLogImpl {
287
- clone(): SessionLogImpl;
288
- tryAdd(
289
- transactions: Transaction[],
290
- newSignature: Signature,
316
+ /**
317
+ * SessionMapImpl - Native implementation of SessionMap
318
+ * One instance per CoValue, owns all session data including header
319
+ */
320
+ export interface SessionMapImpl {
321
+ // === Header ===
322
+ getHeader(): string;
323
+
324
+ // === Transaction Operations ===
325
+ addTransactions(
326
+ sessionId: string,
327
+ signerId: string | undefined,
328
+ transactionsJson: string,
329
+ signature: string,
291
330
  skipVerify: boolean,
292
331
  ): void;
293
- addNewPrivateTransaction(
294
- signerAgent: ControlledAccountOrAgent,
295
- changes: JsonValue[],
296
- keyID: KeyID,
297
- keySecret: KeySecret,
332
+
333
+ makeNewPrivateTransaction(
334
+ sessionId: string,
335
+ signerSecret: string,
336
+ changesJson: string,
337
+ keyId: string,
338
+ keySecret: string,
339
+ metaJson: string | undefined,
298
340
  madeAt: number,
299
- meta: JsonObject | undefined,
300
- ): { signature: Signature; transaction: PrivateTransaction };
301
- addNewTrustingTransaction(
302
- signerAgent: ControlledAccountOrAgent,
303
- changes: JsonValue[],
341
+ ): string; // Returns JSON: { signature, transaction }
342
+
343
+ makeNewTrustingTransaction(
344
+ sessionId: string,
345
+ signerSecret: string,
346
+ changesJson: string,
347
+ metaJson: string | undefined,
304
348
  madeAt: number,
305
- meta: JsonObject | undefined,
306
- ): { signature: Signature; transaction: TrustingTransaction };
307
- decryptNextTransactionChangesJson(
308
- tx_index: number,
309
- key_secret: KeySecret,
310
- ): string;
311
- free(): void;
312
- decryptNextTransactionMetaJson(
313
- tx_index: number,
314
- key_secret: KeySecret,
349
+ ): string; // Returns JSON: { signature, transaction }
350
+
351
+ // === Session Queries ===
352
+ getSessionIds(): string[];
353
+ getTransactionCount(sessionId: string): number; // -1 if not found
354
+ getTransaction(sessionId: string, txIndex: number): Transaction | undefined;
355
+ getSessionTransactions(
356
+ sessionId: string,
357
+ fromIndex: number,
358
+ ): Transaction[] | undefined;
359
+ getLastSignature(sessionId: string): string | undefined;
360
+ getSignatureAfter(sessionId: string, txIndex: number): string | undefined;
361
+ getLastSignatureCheckpoint(sessionId: string): number | undefined; // -1 if no checkpoints, undefined if session not found
362
+
363
+ // === Known State ===
364
+ getKnownState(): {
365
+ id: string;
366
+ header: boolean;
367
+ sessions: Record<string, number>;
368
+ };
369
+ getKnownStateWithStreaming():
370
+ | { id: string; header: boolean; sessions: Record<string, number> }
371
+ | undefined;
372
+ setStreamingKnownState(streamingJson: string): void;
373
+
374
+ // === Deletion ===
375
+ markAsDeleted(): void;
376
+ isDeleted(): boolean;
377
+
378
+ // === Decryption ===
379
+ decryptTransaction(
380
+ sessionId: string,
381
+ txIndex: number,
382
+ keySecret: string,
383
+ ): string | undefined;
384
+ decryptTransactionMeta(
385
+ sessionId: string,
386
+ txIndex: number,
387
+ keySecret: string,
315
388
  ): string | undefined;
316
389
  }
package/src/exports.ts CHANGED
@@ -1,4 +1,8 @@
1
- import { base64URLtoBytes, bytesToBase64url } from "./base64url.js";
1
+ import {
2
+ base64URLtoBytes,
3
+ bytesToBase64url,
4
+ bytesToBase64,
5
+ } from "./base64url.js";
2
6
  import { type RawCoValue } from "./coValue.js";
3
7
  import {
4
8
  CoValueCore,
@@ -23,10 +27,9 @@ import { RawCoPlainText, stringifyOpID } from "./coValues/coPlainText.js";
23
27
  import {
24
28
  BinaryStreamItem,
25
29
  BinaryStreamStart,
26
- CoStreamItem,
27
30
  RawBinaryCoStream,
28
- RawCoStream,
29
- } from "./coValues/coStream.js";
31
+ } from "./coValues/binaryCoStream.js";
32
+ import { CoStreamItem, RawCoStream } from "./coValues/coStream.js";
30
33
  import { EVERYONE, RawGroup } from "./coValues/group.js";
31
34
  import type { Everyone } from "./coValues/group.js";
32
35
  import {
@@ -58,7 +61,7 @@ import type {
58
61
  import type {
59
62
  BinaryCoStreamMeta,
60
63
  BinaryStreamInfo,
61
- } from "./coValues/coStream.js";
64
+ } from "./coValues/binaryCoStream.js";
62
65
  import type { InviteSecret } from "./coValues/group.js";
63
66
  import { AgentSecret, textDecoder, textEncoder } from "./crypto/crypto.js";
64
67
  import type { AgentID, RawCoID, SessionID } from "./ids.js";
@@ -112,6 +115,7 @@ export const cojsonInternals = {
112
115
  expectGroup,
113
116
  base64URLtoBytes,
114
117
  bytesToBase64url,
118
+ bytesToBase64,
115
119
  parseJSON,
116
120
  stableStringify,
117
121
  getDependedOnCoValues,
@@ -229,6 +233,8 @@ export namespace CojsonInternalTypes {
229
233
  export type RawCoID = import("./ids.js").RawCoID;
230
234
  export type ProfileShape = import("./coValues/account.js").ProfileShape;
231
235
  export type SealerSecret = import("./crypto/crypto.js").SealerSecret;
236
+ export type SealerID = import("./crypto/crypto.js").SealerID;
237
+ export type SealedForGroup<T> = import("./crypto/crypto.js").SealedForGroup<T>;
232
238
  export type SignerID = import("./crypto/crypto.js").SignerID;
233
239
  export type SignerSecret = import("./crypto/crypto.js").SignerSecret;
234
240
  export type JsonObject = import("./jsonValue.js").JsonObject;
package/src/localNode.ts CHANGED
@@ -27,6 +27,7 @@ import {
27
27
  import {
28
28
  type InviteSecret,
29
29
  type RawGroup,
30
+ formatGroupSealerValue,
30
31
  secretSeedFromInviteSecret,
31
32
  } from "./coValues/group.js";
32
33
  import { CO_VALUE_LOADING_CONFIG } from "./config.js";
@@ -824,13 +825,19 @@ export class LocalNode {
824
825
 
825
826
  createGroup(
826
827
  uniqueness: CoValueUniqueness = this.crypto.createdNowUnique(),
828
+ options?: { name?: string },
827
829
  ): RawGroup {
828
830
  const account = this.getCurrentAgent();
829
831
 
832
+ const meta =
833
+ options?.name != null && options.name !== ""
834
+ ? { name: options.name }
835
+ : null;
836
+
830
837
  const groupCoValue = this.createCoValue({
831
838
  type: "comap",
832
839
  ruleset: { type: "group", initialAdmin: account.id },
833
- meta: null,
840
+ meta,
834
841
  ...(uniqueness.createdAt !== undefined
835
842
  ? { createdAt: uniqueness.createdAt }
836
843
  : {}),
@@ -859,6 +866,15 @@ export class LocalNode {
859
866
 
860
867
  group.set("readKey", readKey.id, "trusting");
861
868
 
869
+ // Initialize the group sealer (derived deterministically from the read key)
870
+ // Store composite value with readKeyID for deterministic readKey association
871
+ const groupSealer = this.crypto.groupSealerFromReadKey(readKey.secret);
872
+ group.set(
873
+ "groupSealer",
874
+ formatGroupSealerValue(readKey.id, groupSealer.publicKey),
875
+ "trusting",
876
+ );
877
+
862
878
  return group;
863
879
  }
864
880
 
package/src/media.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { RawCoMap } from "./coValues/coMap.js";
2
- import { RawBinaryCoStream } from "./coValues/coStream.js";
2
+ import { RawBinaryCoStream } from "./coValues/binaryCoStream.js";
3
3
 
4
4
  export type ImageDefinition = RawCoMap<{
5
5
  originalSize: [number, number];