cojson 0.17.8 → 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 (73) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +13 -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/sync.d.ts +2 -0
  35. package/dist/sync.d.ts.map +1 -1
  36. package/dist/sync.js +8 -1
  37. package/dist/sync.js.map +1 -1
  38. package/dist/tests/PureJSCrypto.test.d.ts +2 -0
  39. package/dist/tests/PureJSCrypto.test.d.ts.map +1 -0
  40. package/dist/tests/PureJSCrypto.test.js +88 -0
  41. package/dist/tests/PureJSCrypto.test.js.map +1 -0
  42. package/dist/tests/WasmCrypto.test.d.ts +2 -0
  43. package/dist/tests/WasmCrypto.test.d.ts.map +1 -0
  44. package/dist/tests/WasmCrypto.test.js +88 -0
  45. package/dist/tests/WasmCrypto.test.js.map +1 -0
  46. package/dist/tests/coValueCore.test.js +62 -187
  47. package/dist/tests/coValueCore.test.js.map +1 -1
  48. package/dist/tests/coreWasm.test.d.ts +2 -0
  49. package/dist/tests/coreWasm.test.d.ts.map +1 -0
  50. package/dist/tests/coreWasm.test.js +80 -0
  51. package/dist/tests/coreWasm.test.js.map +1 -0
  52. package/dist/tests/sync.test.js +19 -1
  53. package/dist/tests/sync.test.js.map +1 -1
  54. package/dist/tests/testUtils.d.ts +3 -0
  55. package/dist/tests/testUtils.d.ts.map +1 -1
  56. package/dist/tests/testUtils.js +4 -1
  57. package/dist/tests/testUtils.js.map +1 -1
  58. package/package.json +3 -3
  59. package/src/coValueCore/SessionMap.ts +230 -0
  60. package/src/coValueCore/coValueCore.ts +66 -91
  61. package/src/coValueCore/verifiedState.ts +60 -129
  62. package/src/coValues/account.ts +28 -4
  63. package/src/crypto/PureJSCrypto.ts +202 -2
  64. package/src/crypto/WasmCrypto.ts +95 -4
  65. package/src/crypto/crypto.ts +38 -1
  66. package/src/localNode.ts +18 -10
  67. package/src/sync.ts +10 -0
  68. package/src/tests/PureJSCrypto.test.ts +130 -0
  69. package/src/tests/WasmCrypto.test.ts +130 -0
  70. package/src/tests/coValueCore.test.ts +84 -292
  71. package/src/tests/coreWasm.test.ts +142 -0
  72. package/src/tests/sync.test.ts +32 -0
  73. package/src/tests/testUtils.ts +9 -1
@@ -3,23 +3,32 @@ import { ed25519, x25519 } from "@noble/curves/ed25519";
3
3
  import { blake3 } from "@noble/hashes/blake3";
4
4
  import { base58 } from "@scure/base";
5
5
  import { base64URLtoBytes, bytesToBase64url } from "../base64url.js";
6
- import { RawCoID, TransactionID } from "../ids.js";
6
+ import {
7
+ PrivateTransaction,
8
+ Transaction,
9
+ TrustingTransaction,
10
+ } from "../coValueCore/verifiedState.js";
11
+ import { RawCoID, SessionID, TransactionID } from "../ids.js";
7
12
  import { Stringified, stableStringify } from "../jsonStringify.js";
8
13
  import { JsonValue } from "../jsonValue.js";
9
14
  import { logger } from "../logger.js";
10
15
  import {
11
16
  CryptoProvider,
12
17
  Encrypted,
18
+ KeyID,
13
19
  KeySecret,
14
20
  Sealed,
15
21
  SealerID,
16
22
  SealerSecret,
23
+ SessionLogImpl,
17
24
  Signature,
18
25
  SignerID,
19
26
  SignerSecret,
27
+ StreamingHash,
20
28
  textDecoder,
21
29
  textEncoder,
22
30
  } from "./crypto.js";
31
+ import { ControlledAccountOrAgent } from "../coValues/account.js";
23
32
 
24
33
  type Blake3State = ReturnType<typeof blake3.create>;
25
34
 
@@ -67,7 +76,7 @@ export class PureJSCrypto extends CryptoProvider<Blake3State> {
67
76
  return this.blake3HashOnce(input).slice(0, 24);
68
77
  }
69
78
 
70
- protected generateJsonNonce(material: JsonValue): Uint8Array {
79
+ generateJsonNonce(material: JsonValue): Uint8Array {
71
80
  return this.generateNonce(textEncoder.encode(stableStringify(material)));
72
81
  }
73
82
 
@@ -199,4 +208,195 @@ export class PureJSCrypto extends CryptoProvider<Blake3State> {
199
208
  return undefined;
200
209
  }
201
210
  }
211
+
212
+ createSessionLog(
213
+ coID: RawCoID,
214
+ sessionID: SessionID,
215
+ signerID: SignerID,
216
+ ): SessionLogImpl {
217
+ return new PureJSSessionLog(coID, sessionID, signerID, this);
218
+ }
219
+ }
220
+
221
+ export class PureJSSessionLog implements SessionLogImpl {
222
+ transactions: string[] = [];
223
+ lastSignature: Signature | undefined;
224
+ streamingHash: Blake3State;
225
+
226
+ constructor(
227
+ private readonly coID: RawCoID,
228
+ private readonly sessionID: SessionID,
229
+ private readonly signerID: SignerID,
230
+ private readonly crypto: PureJSCrypto,
231
+ ) {
232
+ this.streamingHash = this.crypto.emptyBlake3State();
233
+ }
234
+
235
+ clone(): SessionLogImpl {
236
+ const newLog = new PureJSSessionLog(
237
+ this.coID,
238
+ this.sessionID,
239
+ this.signerID,
240
+ this.crypto,
241
+ );
242
+ newLog.transactions = this.transactions.slice();
243
+ newLog.lastSignature = this.lastSignature;
244
+ newLog.streamingHash = this.crypto.cloneBlake3State(this.streamingHash);
245
+ return newLog;
246
+ }
247
+
248
+ tryAdd(
249
+ transactions: Transaction[],
250
+ newSignature: Signature,
251
+ skipVerify: boolean,
252
+ ): void {
253
+ this.internalTryAdd(
254
+ transactions.map((tx) => stableStringify(tx)),
255
+ newSignature,
256
+ skipVerify,
257
+ );
258
+ }
259
+
260
+ internalTryAdd(
261
+ transactions: string[],
262
+ newSignature: Signature,
263
+ skipVerify: boolean,
264
+ ) {
265
+ if (!skipVerify) {
266
+ const checkHasher = this.crypto.cloneBlake3State(this.streamingHash);
267
+
268
+ for (const tx of transactions) {
269
+ checkHasher.update(textEncoder.encode(tx));
270
+ }
271
+ const newHash = checkHasher.digest();
272
+ const newHashEncoded = `hash_z${base58.encode(newHash)}`;
273
+
274
+ if (!this.crypto.verify(newSignature, newHashEncoded, this.signerID)) {
275
+ throw new Error("Signature verification failed");
276
+ }
277
+ }
278
+
279
+ for (const tx of transactions) {
280
+ this.crypto.blake3IncrementalUpdate(
281
+ this.streamingHash,
282
+ textEncoder.encode(tx),
283
+ );
284
+ this.transactions.push(tx);
285
+ }
286
+
287
+ this.lastSignature = newSignature;
288
+
289
+ return newSignature;
290
+ }
291
+
292
+ expectedHashAfter(transactionsJson: string[]): string {
293
+ const hasher = this.crypto.cloneBlake3State(this.streamingHash);
294
+ for (const tx of transactionsJson) {
295
+ hasher.update(textEncoder.encode(tx));
296
+ }
297
+ const newHash = hasher.digest();
298
+ return `hash_z${base58.encode(newHash)}`;
299
+ }
300
+
301
+ internalAddNewTransaction(
302
+ transaction: string,
303
+ signerAgent: ControlledAccountOrAgent,
304
+ ) {
305
+ this.crypto.blake3IncrementalUpdate(
306
+ this.streamingHash,
307
+ textEncoder.encode(transaction),
308
+ );
309
+ const newHash = this.crypto.blake3DigestForState(this.streamingHash);
310
+ const newHashEncoded = `hash_z${base58.encode(newHash)}`;
311
+ const signature = this.crypto.sign(
312
+ signerAgent.currentSignerSecret(),
313
+ newHashEncoded,
314
+ );
315
+ this.transactions.push(transaction);
316
+ this.lastSignature = signature;
317
+
318
+ return signature;
319
+ }
320
+
321
+ addNewPrivateTransaction(
322
+ signerAgent: ControlledAccountOrAgent,
323
+ changes: JsonValue[],
324
+ keyID: KeyID,
325
+ keySecret: KeySecret,
326
+ madeAt: number,
327
+ ): { signature: Signature; transaction: PrivateTransaction } {
328
+ const encryptedChanges = this.crypto.encrypt(changes, keySecret, {
329
+ in: this.coID,
330
+ tx: { sessionID: this.sessionID, txIndex: this.transactions.length },
331
+ });
332
+ const tx = {
333
+ encryptedChanges: encryptedChanges,
334
+ madeAt: madeAt,
335
+ privacy: "private",
336
+ keyUsed: keyID,
337
+ } satisfies Transaction;
338
+ const signature = this.internalAddNewTransaction(
339
+ stableStringify(tx),
340
+ signerAgent,
341
+ );
342
+ return {
343
+ signature: signature as Signature,
344
+ transaction: tx,
345
+ };
346
+ }
347
+
348
+ addNewTrustingTransaction(
349
+ signerAgent: ControlledAccountOrAgent,
350
+ changes: JsonValue[],
351
+ madeAt: number,
352
+ ): { signature: Signature; transaction: TrustingTransaction } {
353
+ const tx = {
354
+ changes: stableStringify(changes),
355
+ madeAt: madeAt,
356
+ privacy: "trusting",
357
+ } satisfies Transaction;
358
+ const signature = this.internalAddNewTransaction(
359
+ stableStringify(tx),
360
+ signerAgent,
361
+ );
362
+ return {
363
+ signature: signature as Signature,
364
+ transaction: tx,
365
+ };
366
+ }
367
+
368
+ decryptNextTransactionChangesJson(
369
+ txIndex: number,
370
+ keySecret: KeySecret,
371
+ ): string {
372
+ const txJson = this.transactions[txIndex];
373
+ if (!txJson) {
374
+ throw new Error("Transaction not found");
375
+ }
376
+ const tx = JSON.parse(txJson) as Transaction;
377
+ if (tx.privacy === "private") {
378
+ const nOnceMaterial = {
379
+ in: this.coID,
380
+ tx: { sessionID: this.sessionID, txIndex: txIndex },
381
+ };
382
+
383
+ const nOnce = this.crypto.generateJsonNonce(nOnceMaterial);
384
+
385
+ const ciphertext = base64URLtoBytes(
386
+ tx.encryptedChanges.substring("encrypted_U".length),
387
+ );
388
+ const keySecretBytes = base58.decode(
389
+ keySecret.substring("keySecret_z".length),
390
+ );
391
+ const plaintext = xsalsa20(keySecretBytes, nOnce, ciphertext);
392
+
393
+ return textDecoder.decode(plaintext);
394
+ } else {
395
+ return tx.changes;
396
+ }
397
+ }
398
+
399
+ free(): void {
400
+ // no-op
401
+ }
202
402
  }
@@ -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);
package/src/sync.ts CHANGED
@@ -132,6 +132,11 @@ export class SyncManager {
132
132
  peers: { [key: PeerID]: PeerState } = {};
133
133
  local: LocalNode;
134
134
 
135
+ // When true, transactions will not be verified.
136
+ // This is useful when syncing only for storage purposes, with the expectation that
137
+ // the transactions have already been verified by the [trusted] peer that sent them.
138
+ private skipVerify: boolean = false;
139
+
135
140
  peersCounter = metrics.getMeter("cojson").createUpDownCounter("jazz.peers", {
136
141
  description: "Amount of connected peers",
137
142
  valueType: ValueType.INT,
@@ -154,6 +159,10 @@ export class SyncManager {
154
159
 
155
160
  syncState: SyncStateManager;
156
161
 
162
+ disableTransactionVerification() {
163
+ this.skipVerify = true;
164
+ }
165
+
157
166
  peersInPriorityOrder(): PeerState[] {
158
167
  return Object.values(this.peers).sort((a, b) => {
159
168
  const aPriority = a.priority || 0;
@@ -637,6 +646,7 @@ export class SyncManager {
637
646
  undefined,
638
647
  newContentForSession.lastSignature,
639
648
  "immediate",
649
+ this.skipVerify,
640
650
  );
641
651
 
642
652
  if (result.isErr()) {
@@ -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
+ });