cojson 0.17.9 → 0.17.11

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 (86) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +18 -0
  3. package/dist/coValueCore/SessionMap.d.ts +45 -0
  4. package/dist/coValueCore/SessionMap.d.ts.map +1 -0
  5. package/dist/coValueCore/SessionMap.js +118 -0
  6. package/dist/coValueCore/SessionMap.js.map +1 -0
  7. package/dist/coValueCore/coValueCore.d.ts +10 -4
  8. package/dist/coValueCore/coValueCore.d.ts.map +1 -1
  9. package/dist/coValueCore/coValueCore.js +55 -68
  10. package/dist/coValueCore/coValueCore.js.map +1 -1
  11. package/dist/coValueCore/verifiedState.d.ts +15 -19
  12. package/dist/coValueCore/verifiedState.d.ts.map +1 -1
  13. package/dist/coValueCore/verifiedState.js +24 -87
  14. package/dist/coValueCore/verifiedState.js.map +1 -1
  15. package/dist/coValues/account.d.ts +4 -0
  16. package/dist/coValues/account.d.ts.map +1 -1
  17. package/dist/coValues/account.js +24 -4
  18. package/dist/coValues/account.js.map +1 -1
  19. package/dist/coValues/group.d.ts.map +1 -1
  20. package/dist/coValues/group.js +6 -2
  21. package/dist/coValues/group.js.map +1 -1
  22. package/dist/crypto/PureJSCrypto.d.ts +31 -3
  23. package/dist/crypto/PureJSCrypto.d.ts.map +1 -1
  24. package/dist/crypto/PureJSCrypto.js +115 -0
  25. package/dist/crypto/PureJSCrypto.js.map +1 -1
  26. package/dist/crypto/WasmCrypto.d.ts +23 -4
  27. package/dist/crypto/WasmCrypto.d.ts.map +1 -1
  28. package/dist/crypto/WasmCrypto.js +44 -2
  29. package/dist/crypto/WasmCrypto.js.map +1 -1
  30. package/dist/crypto/crypto.d.ts +17 -1
  31. package/dist/crypto/crypto.d.ts.map +1 -1
  32. package/dist/crypto/crypto.js.map +1 -1
  33. package/dist/localNode.d.ts +1 -0
  34. package/dist/localNode.d.ts.map +1 -1
  35. package/dist/localNode.js +10 -5
  36. package/dist/localNode.js.map +1 -1
  37. package/dist/permissions.d.ts +17 -1
  38. package/dist/permissions.d.ts.map +1 -1
  39. package/dist/permissions.js.map +1 -1
  40. package/dist/sync.d.ts.map +1 -1
  41. package/dist/sync.js +55 -49
  42. package/dist/sync.js.map +1 -1
  43. package/dist/tests/PureJSCrypto.test.d.ts +2 -0
  44. package/dist/tests/PureJSCrypto.test.d.ts.map +1 -0
  45. package/dist/tests/PureJSCrypto.test.js +102 -0
  46. package/dist/tests/PureJSCrypto.test.js.map +1 -0
  47. package/dist/tests/WasmCrypto.test.d.ts +2 -0
  48. package/dist/tests/WasmCrypto.test.d.ts.map +1 -0
  49. package/dist/tests/WasmCrypto.test.js +88 -0
  50. package/dist/tests/WasmCrypto.test.js.map +1 -0
  51. package/dist/tests/coValueCore.test.js +62 -187
  52. package/dist/tests/coValueCore.test.js.map +1 -1
  53. package/dist/tests/coreWasm.test.d.ts +2 -0
  54. package/dist/tests/coreWasm.test.d.ts.map +1 -0
  55. package/dist/tests/coreWasm.test.js +80 -0
  56. package/dist/tests/coreWasm.test.js.map +1 -0
  57. package/dist/tests/group.addMember.test.js +6 -11
  58. package/dist/tests/group.addMember.test.js.map +1 -1
  59. package/dist/tests/sync.load.test.js +40 -1
  60. package/dist/tests/sync.load.test.js.map +1 -1
  61. package/dist/tests/sync.test.js +1 -1
  62. package/dist/tests/sync.test.js.map +1 -1
  63. package/dist/tests/testUtils.d.ts +3 -0
  64. package/dist/tests/testUtils.d.ts.map +1 -1
  65. package/dist/tests/testUtils.js +4 -1
  66. package/dist/tests/testUtils.js.map +1 -1
  67. package/package.json +3 -3
  68. package/src/coValueCore/SessionMap.ts +229 -0
  69. package/src/coValueCore/coValueCore.ts +106 -121
  70. package/src/coValueCore/verifiedState.ts +61 -132
  71. package/src/coValues/account.ts +28 -4
  72. package/src/coValues/group.ts +10 -2
  73. package/src/crypto/PureJSCrypto.ts +206 -2
  74. package/src/crypto/WasmCrypto.ts +95 -4
  75. package/src/crypto/crypto.ts +38 -1
  76. package/src/localNode.ts +18 -10
  77. package/src/permissions.ts +17 -1
  78. package/src/sync.ts +63 -59
  79. package/src/tests/PureJSCrypto.test.ts +153 -0
  80. package/src/tests/WasmCrypto.test.ts +128 -0
  81. package/src/tests/coValueCore.test.ts +81 -293
  82. package/src/tests/coreWasm.test.ts +142 -0
  83. package/src/tests/group.addMember.test.ts +69 -63
  84. package/src/tests/sync.load.test.ts +52 -0
  85. package/src/tests/sync.test.ts +0 -2
  86. package/src/tests/testUtils.ts +9 -1
@@ -1,4 +1,6 @@
1
1
  import {
2
+ SessionLog,
3
+ initialize,
2
4
  Blake3Hasher,
3
5
  blake3_empty_state,
4
6
  blake3_hash_once,
@@ -7,16 +9,15 @@ import {
7
9
  encrypt,
8
10
  get_sealer_id,
9
11
  get_signer_id,
10
- initialize,
11
12
  new_ed25519_signing_key,
12
13
  new_x25519_private_key,
13
14
  seal,
14
15
  sign,
15
16
  unseal,
16
17
  verify,
17
- } from "jazz-crypto-rs";
18
+ } from "cojson-core-wasm";
18
19
  import { base64URLtoBytes, bytesToBase64url } from "../base64url.js";
19
- import { RawCoID, TransactionID } from "../ids.js";
20
+ import { RawCoID, SessionID, TransactionID } from "../ids.js";
20
21
  import { Stringified, stableStringify } from "../jsonStringify.js";
21
22
  import { JsonValue } from "../jsonValue.js";
22
23
  import { logger } from "../logger.js";
@@ -24,6 +25,8 @@ import { PureJSCrypto } from "./PureJSCrypto.js";
24
25
  import {
25
26
  CryptoProvider,
26
27
  Encrypted,
28
+ Hash,
29
+ KeyID,
27
30
  KeySecret,
28
31
  Sealed,
29
32
  SealerID,
@@ -34,11 +37,17 @@ import {
34
37
  textDecoder,
35
38
  textEncoder,
36
39
  } from "./crypto.js";
40
+ import { ControlledAccountOrAgent } from "../coValues/account.js";
41
+ import {
42
+ PrivateTransaction,
43
+ Transaction,
44
+ TrustingTransaction,
45
+ } from "../coValueCore/verifiedState.js";
37
46
 
38
47
  type Blake3State = Blake3Hasher;
39
48
 
40
49
  /**
41
- * WebAssembly implementation of the CryptoProvider interface using jazz-crypto-rs.
50
+ * WebAssembly implementation of the CryptoProvider interface using cojson-core-wasm.
42
51
  * This provides the primary implementation using WebAssembly for optimal performance, offering:
43
52
  * - Signing/verifying (Ed25519)
44
53
  * - Encryption/decryption (XSalsa20)
@@ -195,4 +204,86 @@ export class WasmCrypto extends CryptoProvider<Blake3State> {
195
204
  return undefined;
196
205
  }
197
206
  }
207
+
208
+ createSessionLog(coID: RawCoID, sessionID: SessionID, signerID?: SignerID) {
209
+ return new SessionLogAdapter(new SessionLog(coID, sessionID, signerID));
210
+ }
211
+ }
212
+
213
+ class SessionLogAdapter {
214
+ constructor(private readonly sessionLog: SessionLog) {}
215
+
216
+ tryAdd(
217
+ transactions: Transaction[],
218
+ newSignature: Signature,
219
+ skipVerify: boolean,
220
+ ): void {
221
+ this.sessionLog.tryAdd(
222
+ transactions.map((tx) => stableStringify(tx)),
223
+ newSignature,
224
+ skipVerify,
225
+ );
226
+ }
227
+
228
+ addNewPrivateTransaction(
229
+ signerAgent: ControlledAccountOrAgent,
230
+ changes: JsonValue[],
231
+ keyID: KeyID,
232
+ keySecret: KeySecret,
233
+ madeAt: number,
234
+ ): { signature: Signature; transaction: PrivateTransaction } {
235
+ const output = this.sessionLog.addNewPrivateTransaction(
236
+ stableStringify(changes),
237
+ signerAgent.currentSignerSecret(),
238
+ keySecret,
239
+ keyID,
240
+ madeAt,
241
+ );
242
+ const parsedOutput = JSON.parse(output);
243
+ const transaction: PrivateTransaction = {
244
+ privacy: "private",
245
+ madeAt,
246
+ encryptedChanges: parsedOutput.encrypted_changes,
247
+ keyUsed: keyID,
248
+ };
249
+ return { signature: parsedOutput.signature, transaction };
250
+ }
251
+
252
+ addNewTrustingTransaction(
253
+ signerAgent: ControlledAccountOrAgent,
254
+ changes: JsonValue[],
255
+ madeAt: number,
256
+ ): { signature: Signature; transaction: TrustingTransaction } {
257
+ const stringifiedChanges = stableStringify(changes);
258
+ const output = this.sessionLog.addNewTrustingTransaction(
259
+ stringifiedChanges,
260
+ signerAgent.currentSignerSecret(),
261
+ madeAt,
262
+ );
263
+ const transaction: TrustingTransaction = {
264
+ privacy: "trusting",
265
+ madeAt,
266
+ changes: stringifiedChanges,
267
+ };
268
+ return { signature: output as Signature, transaction };
269
+ }
270
+
271
+ decryptNextTransactionChangesJson(
272
+ txIndex: number,
273
+ keySecret: KeySecret,
274
+ ): string {
275
+ const output = this.sessionLog.decryptNextTransactionChangesJson(
276
+ txIndex,
277
+ keySecret,
278
+ );
279
+ return output;
280
+ }
281
+
282
+ free() {
283
+ this.sessionLog.free();
284
+ }
285
+
286
+ clone(): SessionLogAdapter {
287
+ return new SessionLogAdapter(this.sessionLog.clone());
288
+ }
198
289
  }
@@ -1,10 +1,15 @@
1
1
  import { base58 } from "@scure/base";
2
- import { RawAccountID } from "../coValues/account.js";
2
+ import { ControlledAccountOrAgent, RawAccountID } from "../coValues/account.js";
3
3
  import { AgentID, RawCoID, TransactionID } from "../ids.js";
4
4
  import { SessionID } from "../ids.js";
5
5
  import { Stringified, parseJSON, stableStringify } from "../jsonStringify.js";
6
6
  import { JsonValue } from "../jsonValue.js";
7
7
  import { logger } from "../logger.js";
8
+ import {
9
+ PrivateTransaction,
10
+ Transaction,
11
+ TrustingTransaction,
12
+ } from "../coValueCore/verifiedState.js";
8
13
 
9
14
  function randomBytes(bytesLength = 32): Uint8Array {
10
15
  return crypto.getRandomValues(new Uint8Array(bytesLength));
@@ -297,6 +302,12 @@ export abstract class CryptoProvider<Blake3State = any> {
297
302
  newRandomSessionID(accountID: RawAccountID | AgentID): SessionID {
298
303
  return `${accountID}_session_z${base58.encode(this.randomBytes(8))}`;
299
304
  }
305
+
306
+ abstract createSessionLog(
307
+ coID: RawCoID,
308
+ sessionID: SessionID,
309
+ signerID?: SignerID,
310
+ ): SessionLogImpl;
300
311
  }
301
312
 
302
313
  export type Hash = `hash_z${string}`;
@@ -341,3 +352,29 @@ export type KeySecret = `keySecret_z${string}`;
341
352
  export type KeyID = `key_z${string}`;
342
353
 
343
354
  export const secretSeedLength = 32;
355
+
356
+ export interface SessionLogImpl {
357
+ clone(): SessionLogImpl;
358
+ tryAdd(
359
+ transactions: Transaction[],
360
+ newSignature: Signature,
361
+ skipVerify: boolean,
362
+ ): void;
363
+ addNewPrivateTransaction(
364
+ signerAgent: ControlledAccountOrAgent,
365
+ changes: JsonValue[],
366
+ keyID: KeyID,
367
+ keySecret: KeySecret,
368
+ madeAt: number,
369
+ ): { signature: Signature; transaction: PrivateTransaction };
370
+ addNewTrustingTransaction(
371
+ signerAgent: ControlledAccountOrAgent,
372
+ changes: JsonValue[],
373
+ madeAt: number,
374
+ ): { signature: Signature; transaction: TrustingTransaction };
375
+ decryptNextTransactionChangesJson(
376
+ tx_index: number,
377
+ key_secret: KeySecret,
378
+ ): string;
379
+ free(): void;
380
+ }
package/src/localNode.ts CHANGED
@@ -132,17 +132,25 @@ export class LocalNode {
132
132
  return accountOrAgentIDfromSessionID(this.currentSessionID);
133
133
  }
134
134
 
135
+ _cachedCurrentAgent: ControlledAccountOrAgent | undefined;
135
136
  getCurrentAgent(): ControlledAccountOrAgent {
136
- const accountOrAgent = this.getCurrentAccountOrAgentID();
137
- if (isAgentID(accountOrAgent)) {
138
- return new ControlledAgent(this.agentSecret, this.crypto);
137
+ if (!this._cachedCurrentAgent) {
138
+ const accountOrAgent = this.getCurrentAccountOrAgentID();
139
+ if (isAgentID(accountOrAgent)) {
140
+ this._cachedCurrentAgent = new ControlledAgent(
141
+ this.agentSecret,
142
+ this.crypto,
143
+ );
144
+ } else {
145
+ this._cachedCurrentAgent = new ControlledAccount(
146
+ expectAccount(
147
+ this.expectCoValueLoaded(accountOrAgent).getCurrentContent(),
148
+ ),
149
+ this.agentSecret,
150
+ );
151
+ }
139
152
  }
140
- return new ControlledAccount(
141
- expectAccount(
142
- this.expectCoValueLoaded(accountOrAgent).getCurrentContent(),
143
- ),
144
- this.agentSecret,
145
- );
153
+ return this._cachedCurrentAgent;
146
154
  }
147
155
 
148
156
  expectCurrentAccountID(reason: string): RawAccountID {
@@ -360,7 +368,7 @@ export class LocalNode {
360
368
 
361
369
  const coValue = this.putCoValue(
362
370
  id,
363
- new VerifiedState(id, this.crypto, header, new Map()),
371
+ new VerifiedState(id, this.crypto, header),
364
372
  );
365
373
 
366
374
  this.garbageCollector?.trackCoValueAccess(coValue);
@@ -31,7 +31,23 @@ export type PermissionsDef =
31
31
  | { type: "ownedByGroup"; group: RawCoID }
32
32
  | { type: "unsafeAllowAll" };
33
33
 
34
- export type AccountRole = "reader" | "writer" | "admin" | "writeOnly";
34
+ export type AccountRole =
35
+ /**
36
+ * Can read the group's CoValues
37
+ */
38
+ | "reader"
39
+ /**
40
+ * Can read and write to the group's CoValues
41
+ */
42
+ | "writer"
43
+ /**
44
+ * Can read and write to the group, and change group member roles
45
+ */
46
+ | "admin"
47
+ /**
48
+ * Can only write to the group's CoValues and read their own changes
49
+ */
50
+ | "writeOnly";
35
51
 
36
52
  export type Role =
37
53
  | AccountRole
package/src/sync.ts CHANGED
@@ -511,28 +511,31 @@ export class SyncManager {
511
511
  (content) => content.newTransactions,
512
512
  );
513
513
 
514
- for (const dependency of getDependedOnCoValuesFromRawData(
515
- msg.id,
516
- msg.header,
517
- sessionIDs,
518
- transactions,
519
- )) {
520
- const dependencyCoValue = this.local.getCoValue(dependency);
514
+ // If we'll be performing transaction verification, ensure all the dependencies available.
515
+ if (!this.skipVerify) {
516
+ for (const dependency of getDependedOnCoValuesFromRawData(
517
+ msg.id,
518
+ msg.header,
519
+ sessionIDs,
520
+ transactions,
521
+ )) {
522
+ const dependencyCoValue = this.local.getCoValue(dependency);
523
+
524
+ if (!dependencyCoValue.hasVerifiedContent()) {
525
+ coValue.markMissingDependency(dependency);
521
526
 
522
- if (!dependencyCoValue.hasVerifiedContent()) {
523
- coValue.markMissingDependency(dependency);
527
+ const peers = this.getServerPeers();
524
528
 
525
- const peers = this.getServerPeers();
529
+ // if the peer that sent the content is a client, we add it to the list of peers
530
+ // to also ask them for the dependency
531
+ if (peer?.role === "client") {
532
+ peers.push(peer);
533
+ }
526
534
 
527
- // if the peer that sent the content is a client, we add it to the list of peers
528
- // to also ask them for the dependency
529
- if (peer?.role === "client") {
530
- peers.push(peer);
535
+ dependencyCoValue.load(peers);
536
+ } else if (!dependencyCoValue.isAvailable()) {
537
+ coValue.markMissingDependency(dependency);
531
538
  }
532
-
533
- dependencyCoValue.load(peers);
534
- } else if (!dependencyCoValue.isAvailable()) {
535
- coValue.markMissingDependency(dependency);
536
539
  }
537
540
  }
538
541
 
@@ -592,60 +595,61 @@ export class SyncManager {
592
595
  continue;
593
596
  }
594
597
 
595
- const accountId = accountOrAgentIDfromSessionID(sessionID);
598
+ // If we'll be performing transaction verification, ensure the account is available.
599
+ if (!this.skipVerify) {
600
+ const accountId = accountOrAgentIDfromSessionID(sessionID);
596
601
 
597
- if (isAccountID(accountId)) {
598
- const account = this.local.getCoValue(accountId);
602
+ if (isAccountID(accountId)) {
603
+ const account = this.local.getCoValue(accountId);
599
604
 
600
- // We can't verify the transaction without the account, so we delay the session content handling until the account is available
601
- if (!account.isAvailable()) {
602
- // This covers the case where we are getting a new session on an already loaded coValue
603
- // where we need to load the account to get their public key
604
- if (!coValue.missingDependencies.has(accountId)) {
605
- const peers = this.getServerPeers();
605
+ // We can't verify the transaction without the account, so we delay the session content handling until the account is available
606
+ if (!account.isAvailable()) {
607
+ // This covers the case where we are getting a new session on an already loaded coValue
608
+ // where we need to load the account to get their public key
609
+ if (!coValue.missingDependencies.has(accountId)) {
610
+ const peers = this.getServerPeers();
606
611
 
607
- if (peer?.role === "client") {
608
- // if the peer that sent the content is a client, we add it to the list of peers
609
- // to also ask them for the dependency
610
- peers.push(peer);
611
- }
612
+ if (peer?.role === "client") {
613
+ // if the peer that sent the content is a client, we add it to the list of peers
614
+ // to also ask them for the dependency
615
+ peers.push(peer);
616
+ }
612
617
 
613
- account.load(peers);
614
- }
618
+ account.load(peers);
619
+ }
615
620
 
616
- // We need to wait for the account to be available before we can verify the transaction
617
- // Currently doing this by delaying the handleNewContent for the session to when we have the account
618
- //
619
- // This is not the best solution, because the knownState is not updated and the ACK response will be given
620
- // by excluding the session.
621
- // This is good enough implementation for now because the only case for the account to be missing are out-of-order
622
- // dependencies push, so the gap should be short lived.
623
- //
624
- // When we are going to have sharded-peers we should revisit this, and store unverified sessions that are considered as part of the
625
- // knwonState, but not actively used until they can be verified.
626
- void account.waitForAvailable().then(() => {
627
- this.handleNewContent(
628
- {
629
- action: "content",
630
- id: coValue.id,
631
- new: {
632
- [sessionID]: newContentForSession,
621
+ // We need to wait for the account to be available before we can verify the transaction
622
+ // Currently doing this by delaying the handleNewContent for the session to when we have the account
623
+ //
624
+ // This is not the best solution, because the knownState is not updated and the ACK response will be given
625
+ // by excluding the session.
626
+ // This is good enough implementation for now because the only case for the account to be missing are out-of-order
627
+ // dependencies push, so the gap should be short lived.
628
+ //
629
+ // When we are going to have sharded-peers we should revisit this, and store unverified sessions that are considered as part of the
630
+ // knwonState, but not actively used until they can be verified.
631
+ void account.waitForAvailable().then(() => {
632
+ this.handleNewContent(
633
+ {
634
+ action: "content",
635
+ id: coValue.id,
636
+ new: {
637
+ [sessionID]: newContentForSession,
638
+ },
639
+ priority: msg.priority,
633
640
  },
634
- priority: msg.priority,
635
- },
636
- from,
637
- );
638
- });
639
- continue;
641
+ from,
642
+ );
643
+ });
644
+ continue;
645
+ }
640
646
  }
641
647
  }
642
648
 
643
649
  const result = coValue.tryAddTransactions(
644
650
  sessionID,
645
651
  newTransactions,
646
- undefined,
647
652
  newContentForSession.lastSignature,
648
- "immediate",
649
653
  this.skipVerify,
650
654
  );
651
655
 
@@ -0,0 +1,153 @@
1
+ import { assert, beforeEach, describe, expect, it } from "vitest";
2
+ import {
3
+ loadCoValueOrFail,
4
+ setCurrentTestCryptoProvider,
5
+ setupTestNode,
6
+ setupTestAccount,
7
+ randomAgentAndSessionID,
8
+ } from "./testUtils";
9
+ import { PureJSCrypto } from "../crypto/PureJSCrypto";
10
+ import { stableStringify } from "../jsonStringify";
11
+
12
+ const jsCrypto = await PureJSCrypto.create();
13
+ setCurrentTestCryptoProvider(jsCrypto);
14
+
15
+ let syncServer: ReturnType<typeof setupTestNode>;
16
+
17
+ beforeEach(() => {
18
+ syncServer = setupTestNode({ isSyncServer: true });
19
+ });
20
+
21
+ // A suite of tests focused on high-level tests that verify:
22
+ // - Keys creation and unsealing
23
+ // - Signature creation and verification
24
+ // - Encryption and decryption of values
25
+ describe("PureJSCrypto", () => {
26
+ it("successfully creates a private CoValue and reads it in another session", async () => {
27
+ const client = setupTestNode({
28
+ connected: true,
29
+ });
30
+
31
+ const group = client.node.createGroup();
32
+ const map = group.createMap();
33
+ map.set("count", 0, "private");
34
+ map.set("count", 1, "private");
35
+ map.set("count", 2, "private");
36
+
37
+ const client2 = client.spawnNewSession();
38
+
39
+ const mapInTheOtherSession = await loadCoValueOrFail(client2.node, map.id);
40
+
41
+ expect(mapInTheOtherSession.get("count")).toEqual(2);
42
+ });
43
+
44
+ it("successfully updates a private CoValue and reads it in another session", async () => {
45
+ const client = setupTestNode({
46
+ connected: true,
47
+ });
48
+
49
+ const group = client.node.createGroup();
50
+ const map = group.createMap();
51
+ map.set("count", 0, "private");
52
+ map.set("count", 1, "private");
53
+ map.set("count", 2, "private");
54
+
55
+ const client2 = client.spawnNewSession();
56
+
57
+ const mapInTheOtherSession = await loadCoValueOrFail(client2.node, map.id);
58
+ mapInTheOtherSession.set("count", 3, "private");
59
+
60
+ await mapInTheOtherSession.core.waitForSync();
61
+
62
+ expect(mapInTheOtherSession.get("count")).toEqual(3);
63
+ });
64
+
65
+ it("can invite another account to a group and share a private CoValue", async () => {
66
+ const client = setupTestNode({
67
+ connected: true,
68
+ });
69
+ const account = await setupTestAccount({
70
+ connected: true,
71
+ });
72
+
73
+ const group = client.node.createGroup();
74
+ const invite = group.createInvite("admin");
75
+
76
+ await account.node.acceptInvite(group.id, invite);
77
+
78
+ const map = group.createMap();
79
+ map.set("secret", "private-data", "private");
80
+
81
+ // The other account should be able to read the private value
82
+ const mapInOtherSession = await loadCoValueOrFail(account.node, map.id);
83
+ expect(mapInOtherSession.get("secret")).toEqual("private-data");
84
+
85
+ mapInOtherSession.set("secret", "modified", "private");
86
+
87
+ await mapInOtherSession.core.waitForSync();
88
+
89
+ expect(map.get("secret")).toEqual("modified");
90
+ });
91
+
92
+ it("rejects sessions with invalid signatures", async () => {
93
+ const client = setupTestNode({
94
+ connected: true,
95
+ });
96
+
97
+ const group = client.node.createGroup();
98
+ const map = group.createMap();
99
+ map.set("count", 0, "trusting");
100
+
101
+ // Create a new session with the same agent
102
+ const client2 = client.spawnNewSession();
103
+
104
+ // This should work normally
105
+ const mapInOtherSession = await loadCoValueOrFail(client2.node, map.id);
106
+ expect(mapInOtherSession.get("count")).toEqual(0);
107
+
108
+ mapInOtherSession.core.tryAddTransactions(
109
+ client2.node.currentSessionID,
110
+ [
111
+ {
112
+ privacy: "trusting",
113
+ changes: stableStringify([{ op: "set", key: "count", value: 1 }]),
114
+ madeAt: Date.now(),
115
+ },
116
+ ],
117
+ "signature_z12345678",
118
+ true,
119
+ );
120
+
121
+ const content =
122
+ mapInOtherSession.core.verified.newContentSince(undefined)?.[0];
123
+ assert(content);
124
+
125
+ client.node.syncManager.handleNewContent(content, "storage");
126
+
127
+ expect(map.get("count")).toEqual(0);
128
+ });
129
+ });
130
+
131
+ describe("PureJSSessionLog", () => {
132
+ it("fails to verify signatures without a signer ID", async () => {
133
+ const agentSecret = jsCrypto.newRandomAgentSecret();
134
+ const sessionID = jsCrypto.newRandomSessionID(
135
+ jsCrypto.getAgentID(agentSecret),
136
+ );
137
+
138
+ const sessionLog = jsCrypto.createSessionLog("co_z12345678", sessionID);
139
+ expect(() =>
140
+ sessionLog.tryAdd(
141
+ [
142
+ {
143
+ privacy: "trusting",
144
+ changes: stableStringify([{ op: "set", key: "count", value: 1 }]),
145
+ madeAt: Date.now(),
146
+ },
147
+ ],
148
+ "signature_z12345678",
149
+ false,
150
+ ),
151
+ ).toThrow("Tried to add transactions without signer ID");
152
+ });
153
+ });