cojson 0.17.9 → 0.17.10

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 (65) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +7 -0
  3. package/dist/coValueCore/SessionMap.d.ts +44 -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 +1 -0
  8. package/dist/coValueCore/coValueCore.d.ts.map +1 -1
  9. package/dist/coValueCore/coValueCore.js +40 -61
  10. package/dist/coValueCore/coValueCore.js.map +1 -1
  11. package/dist/coValueCore/verifiedState.d.ts +14 -18
  12. package/dist/coValueCore/verifiedState.d.ts.map +1 -1
  13. package/dist/coValueCore/verifiedState.js +23 -86
  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/crypto/PureJSCrypto.d.ts +31 -3
  20. package/dist/crypto/PureJSCrypto.d.ts.map +1 -1
  21. package/dist/crypto/PureJSCrypto.js +112 -0
  22. package/dist/crypto/PureJSCrypto.js.map +1 -1
  23. package/dist/crypto/WasmCrypto.d.ts +23 -4
  24. package/dist/crypto/WasmCrypto.d.ts.map +1 -1
  25. package/dist/crypto/WasmCrypto.js +44 -2
  26. package/dist/crypto/WasmCrypto.js.map +1 -1
  27. package/dist/crypto/crypto.d.ts +17 -1
  28. package/dist/crypto/crypto.d.ts.map +1 -1
  29. package/dist/crypto/crypto.js.map +1 -1
  30. package/dist/localNode.d.ts +1 -0
  31. package/dist/localNode.d.ts.map +1 -1
  32. package/dist/localNode.js +10 -5
  33. package/dist/localNode.js.map +1 -1
  34. package/dist/tests/PureJSCrypto.test.d.ts +2 -0
  35. package/dist/tests/PureJSCrypto.test.d.ts.map +1 -0
  36. package/dist/tests/PureJSCrypto.test.js +88 -0
  37. package/dist/tests/PureJSCrypto.test.js.map +1 -0
  38. package/dist/tests/WasmCrypto.test.d.ts +2 -0
  39. package/dist/tests/WasmCrypto.test.d.ts.map +1 -0
  40. package/dist/tests/WasmCrypto.test.js +88 -0
  41. package/dist/tests/WasmCrypto.test.js.map +1 -0
  42. package/dist/tests/coValueCore.test.js +62 -187
  43. package/dist/tests/coValueCore.test.js.map +1 -1
  44. package/dist/tests/coreWasm.test.d.ts +2 -0
  45. package/dist/tests/coreWasm.test.d.ts.map +1 -0
  46. package/dist/tests/coreWasm.test.js +80 -0
  47. package/dist/tests/coreWasm.test.js.map +1 -0
  48. package/dist/tests/testUtils.d.ts +3 -0
  49. package/dist/tests/testUtils.d.ts.map +1 -1
  50. package/dist/tests/testUtils.js +4 -1
  51. package/dist/tests/testUtils.js.map +1 -1
  52. package/package.json +3 -3
  53. package/src/coValueCore/SessionMap.ts +230 -0
  54. package/src/coValueCore/coValueCore.ts +66 -91
  55. package/src/coValueCore/verifiedState.ts +60 -129
  56. package/src/coValues/account.ts +28 -4
  57. package/src/crypto/PureJSCrypto.ts +202 -2
  58. package/src/crypto/WasmCrypto.ts +95 -4
  59. package/src/crypto/crypto.ts +38 -1
  60. package/src/localNode.ts +18 -10
  61. package/src/tests/PureJSCrypto.test.ts +130 -0
  62. package/src/tests/WasmCrypto.test.ts +130 -0
  63. package/src/tests/coValueCore.test.ts +84 -292
  64. package/src/tests/coreWasm.test.ts +142 -0
  65. package/src/tests/testUtils.ts +9 -1
@@ -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);
@@ -0,0 +1,130 @@
1
+ import { assert, beforeEach, describe, expect, it } from "vitest";
2
+ import {
3
+ loadCoValueOrFail,
4
+ setCurrentTestCryptoProvider,
5
+ setupTestNode,
6
+ setupTestAccount,
7
+ } from "./testUtils";
8
+ import { PureJSCrypto } from "../crypto/PureJSCrypto";
9
+ import { stableStringify } from "../jsonStringify";
10
+
11
+ const jsCrypto = await PureJSCrypto.create();
12
+ setCurrentTestCryptoProvider(jsCrypto);
13
+
14
+ let syncServer: ReturnType<typeof setupTestNode>;
15
+
16
+ beforeEach(() => {
17
+ syncServer = setupTestNode({ isSyncServer: true });
18
+ });
19
+
20
+ // A suite of tests focused on high-level tests that verify:
21
+ // - Keys creation and unsealing
22
+ // - Signature creation and verification
23
+ // - Encryption and decryption of values
24
+ describe("PureJSCrypto", () => {
25
+ it("successfully creates a private CoValue and reads it in another session", async () => {
26
+ const client = setupTestNode({
27
+ connected: true,
28
+ });
29
+
30
+ const group = client.node.createGroup();
31
+ const map = group.createMap();
32
+ map.set("count", 0, "private");
33
+ map.set("count", 1, "private");
34
+ map.set("count", 2, "private");
35
+
36
+ const client2 = client.spawnNewSession();
37
+
38
+ const mapInTheOtherSession = await loadCoValueOrFail(client2.node, map.id);
39
+
40
+ expect(mapInTheOtherSession.get("count")).toEqual(2);
41
+ });
42
+
43
+ it("successfully updates a private CoValue and reads it in another session", async () => {
44
+ const client = setupTestNode({
45
+ connected: true,
46
+ });
47
+
48
+ const group = client.node.createGroup();
49
+ const map = group.createMap();
50
+ map.set("count", 0, "private");
51
+ map.set("count", 1, "private");
52
+ map.set("count", 2, "private");
53
+
54
+ const client2 = client.spawnNewSession();
55
+
56
+ const mapInTheOtherSession = await loadCoValueOrFail(client2.node, map.id);
57
+ mapInTheOtherSession.set("count", 3, "private");
58
+
59
+ await mapInTheOtherSession.core.waitForSync();
60
+
61
+ expect(mapInTheOtherSession.get("count")).toEqual(3);
62
+ });
63
+
64
+ it("can invite another account to a group and share a private CoValue", async () => {
65
+ const client = setupTestNode({
66
+ connected: true,
67
+ });
68
+ const account = await setupTestAccount({
69
+ connected: true,
70
+ });
71
+
72
+ const group = client.node.createGroup();
73
+ const invite = group.createInvite("admin");
74
+
75
+ await account.node.acceptInvite(group.id, invite);
76
+
77
+ const map = group.createMap();
78
+ map.set("secret", "private-data", "private");
79
+
80
+ // The other account should be able to read the private value
81
+ const mapInOtherSession = await loadCoValueOrFail(account.node, map.id);
82
+ expect(mapInOtherSession.get("secret")).toEqual("private-data");
83
+
84
+ mapInOtherSession.set("secret", "modified", "private");
85
+
86
+ await mapInOtherSession.core.waitForSync();
87
+
88
+ expect(map.get("secret")).toEqual("modified");
89
+ });
90
+
91
+ it("rejects sessions with invalid signatures", async () => {
92
+ const client = setupTestNode({
93
+ connected: true,
94
+ });
95
+
96
+ const group = client.node.createGroup();
97
+ const map = group.createMap();
98
+ map.set("count", 0, "trusting");
99
+
100
+ // Create a new session with the same agent
101
+ const client2 = client.spawnNewSession();
102
+
103
+ // This should work normally
104
+ const mapInOtherSession = await loadCoValueOrFail(client2.node, map.id);
105
+ expect(mapInOtherSession.get("count")).toEqual(0);
106
+
107
+ mapInOtherSession.core.tryAddTransactions(
108
+ client2.node.currentSessionID,
109
+ [
110
+ {
111
+ privacy: "trusting",
112
+ changes: stableStringify([{ op: "set", key: "count", value: 1 }]),
113
+ madeAt: Date.now(),
114
+ },
115
+ ],
116
+ "hash_z12345678",
117
+ "signature_z12345678",
118
+ "immediate",
119
+ true,
120
+ );
121
+
122
+ const content =
123
+ mapInOtherSession.core.verified.newContentSince(undefined)?.[0];
124
+ assert(content);
125
+
126
+ client.node.syncManager.handleNewContent(content, "storage");
127
+
128
+ expect(map.get("count")).toEqual(0);
129
+ });
130
+ });
@@ -0,0 +1,130 @@
1
+ import { assert, beforeEach, describe, expect, it } from "vitest";
2
+ import {
3
+ loadCoValueOrFail,
4
+ setCurrentTestCryptoProvider,
5
+ setupTestNode,
6
+ setupTestAccount,
7
+ } from "./testUtils";
8
+ import { stableStringify } from "../jsonStringify";
9
+ import { WasmCrypto } from "../crypto/WasmCrypto";
10
+
11
+ const wasmCrypto = await WasmCrypto.create();
12
+ setCurrentTestCryptoProvider(wasmCrypto);
13
+
14
+ let syncServer: ReturnType<typeof setupTestNode>;
15
+
16
+ beforeEach(() => {
17
+ syncServer = setupTestNode({ isSyncServer: true });
18
+ });
19
+
20
+ // A suite of tests focused on high-level tests that verify:
21
+ // - Keys creation and unsealing
22
+ // - Signature creation and verification
23
+ // - Encryption and decryption of values
24
+ describe("WasmCrypto", () => {
25
+ it("successfully creates a private CoValue and reads it in another session", async () => {
26
+ const client = setupTestNode({
27
+ connected: true,
28
+ });
29
+
30
+ const group = client.node.createGroup();
31
+ const map = group.createMap();
32
+ map.set("count", 0, "private");
33
+ map.set("count", 1, "private");
34
+ map.set("count", 2, "private");
35
+
36
+ const client2 = client.spawnNewSession();
37
+
38
+ const mapInTheOtherSession = await loadCoValueOrFail(client2.node, map.id);
39
+
40
+ expect(mapInTheOtherSession.get("count")).toEqual(2);
41
+ });
42
+
43
+ it("successfully updates a private CoValue and reads it in another session", async () => {
44
+ const client = setupTestNode({
45
+ connected: true,
46
+ });
47
+
48
+ const group = client.node.createGroup();
49
+ const map = group.createMap();
50
+ map.set("count", 0, "private");
51
+ map.set("count", 1, "private");
52
+ map.set("count", 2, "private");
53
+
54
+ const client2 = client.spawnNewSession();
55
+
56
+ const mapInTheOtherSession = await loadCoValueOrFail(client2.node, map.id);
57
+ mapInTheOtherSession.set("count", 3, "private");
58
+
59
+ await mapInTheOtherSession.core.waitForSync();
60
+
61
+ expect(mapInTheOtherSession.get("count")).toEqual(3);
62
+ });
63
+
64
+ it("can invite another account to a group and share a private CoValue", async () => {
65
+ const client = setupTestNode({
66
+ connected: true,
67
+ });
68
+ const account = await setupTestAccount({
69
+ connected: true,
70
+ });
71
+
72
+ const group = client.node.createGroup();
73
+ const invite = group.createInvite("admin");
74
+
75
+ await account.node.acceptInvite(group.id, invite);
76
+
77
+ const map = group.createMap();
78
+ map.set("secret", "private-data", "private");
79
+
80
+ // The other account should be able to read the private value
81
+ const mapInOtherSession = await loadCoValueOrFail(account.node, map.id);
82
+ expect(mapInOtherSession.get("secret")).toEqual("private-data");
83
+
84
+ mapInOtherSession.set("secret", "modified", "private");
85
+
86
+ await mapInOtherSession.core.waitForSync();
87
+
88
+ expect(map.get("secret")).toEqual("modified");
89
+ });
90
+
91
+ it("rejects sessions with invalid signatures", async () => {
92
+ const client = setupTestNode({
93
+ connected: true,
94
+ });
95
+
96
+ const group = client.node.createGroup();
97
+ const map = group.createMap();
98
+ map.set("count", 0, "trusting");
99
+
100
+ // Create a new session with the same agent
101
+ const client2 = client.spawnNewSession();
102
+
103
+ // This should work normally
104
+ const mapInOtherSession = await loadCoValueOrFail(client2.node, map.id);
105
+ expect(mapInOtherSession.get("count")).toEqual(0);
106
+
107
+ mapInOtherSession.core.tryAddTransactions(
108
+ client2.node.currentSessionID,
109
+ [
110
+ {
111
+ privacy: "trusting",
112
+ changes: stableStringify([{ op: "set", key: "count", value: 1 }]),
113
+ madeAt: Date.now(),
114
+ },
115
+ ],
116
+ "hash_z12345678",
117
+ "signature_z12345678",
118
+ "immediate",
119
+ true,
120
+ );
121
+
122
+ const content =
123
+ mapInOtherSession.core.verified.newContentSince(undefined)?.[0];
124
+ assert(content);
125
+
126
+ client.node.syncManager.handleNewContent(content, "storage");
127
+
128
+ expect(map.get("count")).toEqual(0);
129
+ });
130
+ });