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
@@ -0,0 +1,230 @@
1
+ import { Result, err, ok } from "neverthrow";
2
+ import { ControlledAccountOrAgent } from "../coValues/account.js";
3
+ import type {
4
+ CryptoProvider,
5
+ Hash,
6
+ KeyID,
7
+ KeySecret,
8
+ SessionLogImpl,
9
+ Signature,
10
+ SignerID,
11
+ } from "../crypto/crypto.js";
12
+ import { RawCoID, SessionID } from "../ids.js";
13
+ import { parseJSON, stableStringify, Stringified } from "../jsonStringify.js";
14
+ import { JsonValue } from "../jsonValue.js";
15
+ import { CoValueKnownState } from "../sync.js";
16
+ import { TryAddTransactionsError } from "./coValueCore.js";
17
+ import { Transaction } from "./verifiedState.js";
18
+ import { exceedsRecommendedSize } from "../coValueContentMessage.js";
19
+
20
+ export type SessionLog = {
21
+ signerID: SignerID;
22
+ impl: SessionLogImpl;
23
+ transactions: Transaction[];
24
+ lastSignature: Signature | undefined;
25
+ signatureAfter: { [txIdx: number]: Signature | undefined };
26
+ txSizeSinceLastInbetweenSignature: number;
27
+ };
28
+
29
+ export class SessionMap {
30
+ sessions: Map<SessionID, SessionLog> = new Map();
31
+
32
+ constructor(
33
+ private readonly id: RawCoID,
34
+ private readonly crypto: CryptoProvider,
35
+ ) {}
36
+
37
+ get(sessionID: SessionID): SessionLog | undefined {
38
+ return this.sessions.get(sessionID);
39
+ }
40
+
41
+ private getOrCreateSessionLog(
42
+ sessionID: SessionID,
43
+ signerID: SignerID,
44
+ ): SessionLog {
45
+ let sessionLog = this.sessions.get(sessionID);
46
+ if (!sessionLog) {
47
+ sessionLog = {
48
+ signerID,
49
+ impl: this.crypto.createSessionLog(this.id, sessionID, signerID),
50
+ transactions: [],
51
+ lastSignature: undefined,
52
+ signatureAfter: {},
53
+ txSizeSinceLastInbetweenSignature: 0,
54
+ };
55
+ this.sessions.set(sessionID, sessionLog);
56
+ }
57
+
58
+ return sessionLog;
59
+ }
60
+
61
+ addTransaction(
62
+ sessionID: SessionID,
63
+ signerID: SignerID,
64
+ newTransactions: Transaction[],
65
+ newSignature: Signature,
66
+ skipVerify: boolean = false,
67
+ ): Result<true, TryAddTransactionsError> {
68
+ const sessionLog = this.getOrCreateSessionLog(sessionID, signerID);
69
+
70
+ try {
71
+ sessionLog.impl.tryAdd(newTransactions, newSignature, skipVerify);
72
+
73
+ this.addTransactionsToJsLog(sessionLog, newTransactions, newSignature);
74
+
75
+ return ok(true as const);
76
+ } catch (e) {
77
+ return err({
78
+ type: "InvalidSignature",
79
+ id: this.id,
80
+ sessionID,
81
+ newSignature,
82
+ signerID,
83
+ } satisfies TryAddTransactionsError);
84
+ }
85
+ }
86
+
87
+ makeNewPrivateTransaction(
88
+ sessionID: SessionID,
89
+ signerAgent: ControlledAccountOrAgent,
90
+ changes: JsonValue[],
91
+ keyID: KeyID,
92
+ keySecret: KeySecret,
93
+ ): { signature: Signature; transaction: Transaction } {
94
+ const sessionLog = this.getOrCreateSessionLog(
95
+ sessionID,
96
+ signerAgent.currentSignerID(),
97
+ );
98
+
99
+ const madeAt = Date.now();
100
+
101
+ const result = sessionLog.impl.addNewPrivateTransaction(
102
+ signerAgent,
103
+ changes,
104
+ keyID,
105
+ keySecret,
106
+ madeAt,
107
+ );
108
+
109
+ this.addTransactionsToJsLog(
110
+ sessionLog,
111
+ [result.transaction],
112
+ result.signature,
113
+ );
114
+
115
+ return result;
116
+ }
117
+
118
+ makeNewTrustingTransaction(
119
+ sessionID: SessionID,
120
+ signerAgent: ControlledAccountOrAgent,
121
+ changes: JsonValue[],
122
+ ): { signature: Signature; transaction: Transaction } {
123
+ const sessionLog = this.getOrCreateSessionLog(
124
+ sessionID,
125
+ signerAgent.currentSignerID(),
126
+ );
127
+
128
+ const madeAt = Date.now();
129
+
130
+ const result = sessionLog.impl.addNewTrustingTransaction(
131
+ signerAgent,
132
+ changes,
133
+ madeAt,
134
+ );
135
+
136
+ this.addTransactionsToJsLog(
137
+ sessionLog,
138
+ [result.transaction],
139
+ result.signature,
140
+ );
141
+
142
+ return result;
143
+ }
144
+
145
+ private addTransactionsToJsLog(
146
+ sessionLog: SessionLog,
147
+ newTransactions: Transaction[],
148
+ signature: Signature,
149
+ ) {
150
+ for (const tx of newTransactions) {
151
+ sessionLog.transactions.push(tx);
152
+ }
153
+ sessionLog.lastSignature = signature;
154
+
155
+ sessionLog.txSizeSinceLastInbetweenSignature += newTransactions.reduce(
156
+ (sum, tx) =>
157
+ sum +
158
+ (tx.privacy === "private"
159
+ ? tx.encryptedChanges.length
160
+ : tx.changes.length),
161
+ 0,
162
+ );
163
+
164
+ if (exceedsRecommendedSize(sessionLog.txSizeSinceLastInbetweenSignature)) {
165
+ sessionLog.signatureAfter[sessionLog.transactions.length - 1] = signature;
166
+ sessionLog.txSizeSinceLastInbetweenSignature = 0;
167
+ }
168
+ }
169
+
170
+ knownState(): CoValueKnownState {
171
+ const sessions: CoValueKnownState["sessions"] = {};
172
+ for (const [sessionID, sessionLog] of this.sessions.entries()) {
173
+ sessions[sessionID] = sessionLog.transactions.length;
174
+ }
175
+ return { id: this.id, header: true, sessions };
176
+ }
177
+
178
+ decryptTransaction(
179
+ sessionID: SessionID,
180
+ txIndex: number,
181
+ keySecret: KeySecret,
182
+ ): JsonValue[] | undefined {
183
+ const sessionLog = this.sessions.get(sessionID);
184
+ if (!sessionLog) {
185
+ return undefined;
186
+ }
187
+ const decrypted = sessionLog.impl.decryptNextTransactionChangesJson(
188
+ txIndex,
189
+ keySecret,
190
+ );
191
+ if (!decrypted) {
192
+ return undefined;
193
+ }
194
+ return parseJSON(decrypted as Stringified<JsonValue[] | undefined>);
195
+ }
196
+
197
+ get size() {
198
+ return this.sessions.size;
199
+ }
200
+
201
+ entries() {
202
+ return this.sessions.entries();
203
+ }
204
+
205
+ values() {
206
+ return this.sessions.values();
207
+ }
208
+
209
+ keys() {
210
+ return this.sessions.keys();
211
+ }
212
+
213
+ clone(): SessionMap {
214
+ const clone = new SessionMap(this.id, this.crypto);
215
+
216
+ for (const [sessionID, sessionLog] of this.sessions) {
217
+ clone.sessions.set(sessionID, {
218
+ impl: sessionLog.impl.clone(),
219
+ transactions: sessionLog.transactions.slice(),
220
+ lastSignature: sessionLog.lastSignature,
221
+ signatureAfter: { ...sessionLog.signatureAfter },
222
+ txSizeSinceLastInbetweenSignature:
223
+ sessionLog.txSizeSinceLastInbetweenSignature,
224
+ signerID: sessionLog.signerID,
225
+ });
226
+ }
227
+
228
+ return clone;
229
+ }
230
+ }
@@ -27,6 +27,7 @@ import { accountOrAgentIDfromSessionID } from "../typeUtils/accountOrAgentIDfrom
27
27
  import { expectGroup } from "../typeUtils/expectGroup.js";
28
28
  import { getDependedOnCoValuesFromRawData } from "./utils.js";
29
29
  import { CoValueHeader, Transaction, VerifiedState } from "./verifiedState.js";
30
+ import { SessionMap } from "./SessionMap.js";
30
31
 
31
32
  export function idforHeader(
32
33
  header: CoValueHeader,
@@ -95,12 +96,7 @@ export class CoValueCore {
95
96
  this.crypto = node.crypto;
96
97
  if ("header" in init) {
97
98
  this.id = idforHeader(init.header, node.crypto);
98
- this._verified = new VerifiedState(
99
- this.id,
100
- node.crypto,
101
- init.header,
102
- new Map(),
103
- );
99
+ this._verified = new VerifiedState(this.id, node.crypto, init.header);
104
100
  } else {
105
101
  this.id = init.id;
106
102
  this._verified = null;
@@ -298,7 +294,7 @@ export class CoValueCore {
298
294
  this.id,
299
295
  this.node.crypto,
300
296
  header,
301
- new Map(),
297
+ new SessionMap(this.id, this.node.crypto),
302
298
  streamingKnownState,
303
299
  );
304
300
 
@@ -467,19 +463,7 @@ export class CoValueCore {
467
463
  );
468
464
 
469
465
  if (result.isOk()) {
470
- if (
471
- this._cachedContent &&
472
- "processNewTransactions" in this._cachedContent &&
473
- typeof this._cachedContent.processNewTransactions === "function"
474
- ) {
475
- this._cachedContent.processNewTransactions();
476
- } else {
477
- this._cachedContent = undefined;
478
- }
479
-
480
- this._cachedDependentOn = undefined;
481
-
482
- this.notifyUpdate(notifyMode);
466
+ this.updateContentAndNotifyUpdate(notifyMode);
483
467
  }
484
468
 
485
469
  return result;
@@ -489,6 +473,22 @@ export class CoValueCore {
489
473
  deferredUpdates = 0;
490
474
  nextDeferredNotify: Promise<void> | undefined;
491
475
 
476
+ updateContentAndNotifyUpdate(notifyMode: "immediate" | "deferred") {
477
+ if (
478
+ this._cachedContent &&
479
+ "processNewTransactions" in this._cachedContent &&
480
+ typeof this._cachedContent.processNewTransactions === "function"
481
+ ) {
482
+ this._cachedContent.processNewTransactions();
483
+ } else {
484
+ this._cachedContent = undefined;
485
+ }
486
+
487
+ this._cachedDependentOn = undefined;
488
+
489
+ this.notifyUpdate(notifyMode);
490
+ }
491
+
492
492
  notifyUpdate(notifyMode: "immediate" | "deferred") {
493
493
  if (this.listeners.size === 0) {
494
494
  return;
@@ -556,9 +556,18 @@ export class CoValueCore {
556
556
  );
557
557
  }
558
558
 
559
- const madeAt = Date.now();
559
+ // This is an ugly hack to get a unique but stable session ID for editing the current account
560
+ const sessionID =
561
+ this.verified.header.meta?.type === "account"
562
+ ? (this.node.currentSessionID.replace(
563
+ this.node.getCurrentAgent().id,
564
+ this.node.getCurrentAgent().currentAgentID(),
565
+ ) as SessionID)
566
+ : this.node.currentSessionID;
567
+
568
+ const signerAgent = this.node.getCurrentAgent();
560
569
 
561
- let transaction: Transaction;
570
+ let result: { signature: Signature; transaction: Transaction };
562
571
 
563
572
  if (privacy === "private") {
564
573
  const { secret: keySecret, id: keyID } = this.getCurrentReadKey();
@@ -567,69 +576,42 @@ export class CoValueCore {
567
576
  throw new Error("Can't make transaction without read key secret");
568
577
  }
569
578
 
570
- const encrypted = this.crypto.encryptForTransaction(changes, keySecret, {
571
- in: this.id,
572
- tx: this.nextTransactionID(),
573
- });
574
-
575
- this._decryptionCache[encrypted] = changes;
579
+ result = this.verified.makeNewPrivateTransaction(
580
+ sessionID,
581
+ signerAgent,
582
+ changes,
583
+ keyID,
584
+ keySecret,
585
+ );
576
586
 
577
- transaction = {
578
- privacy: "private",
579
- madeAt,
580
- keyUsed: keyID,
581
- encryptedChanges: encrypted,
582
- };
587
+ if (result.transaction.privacy === "private") {
588
+ this._decryptionCache[result.transaction.encryptedChanges] = changes;
589
+ }
583
590
  } else {
584
- transaction = {
585
- privacy: "trusting",
586
- madeAt,
587
- changes: stableStringify(changes),
588
- };
591
+ result = this.verified.makeNewTrustingTransaction(
592
+ sessionID,
593
+ signerAgent,
594
+ changes,
595
+ );
589
596
  }
590
597
 
591
- // This is an ugly hack to get a unique but stable session ID for editing the current account
592
- const sessionID =
593
- this.verified.header.meta?.type === "account"
594
- ? (this.node.currentSessionID.replace(
595
- this.node.getCurrentAgent().id,
596
- this.node.getCurrentAgent().currentAgentID(),
597
- ) as SessionID)
598
- : this.node.currentSessionID;
598
+ const { transaction, signature } = result;
599
599
 
600
- const { expectedNewHash, newStreamingHash } =
601
- this.verified.expectedNewHashAfter(sessionID, [transaction]);
600
+ this.node.syncManager.recordTransactionsSize([transaction], "local");
602
601
 
603
- const signature = this.crypto.sign(
604
- this.node.getCurrentAgent().currentSignerSecret(),
605
- expectedNewHash,
606
- );
602
+ const session = this.verified.sessions.get(sessionID);
603
+ const txIdx = session ? session.transactions.length - 1 : 0;
607
604
 
608
- const success = this.tryAddTransactions(
605
+ this.updateContentAndNotifyUpdate("immediate");
606
+ this.node.syncManager.syncLocalTransaction(
607
+ this.verified,
608
+ transaction,
609
609
  sessionID,
610
- [transaction],
611
- expectedNewHash,
612
610
  signature,
613
- "immediate",
614
- true,
615
- newStreamingHash,
616
- )._unsafeUnwrap({ withStackTrace: true });
617
-
618
- if (success) {
619
- const session = this.verified.sessions.get(sessionID);
620
- const txIdx = session ? session.transactions.length - 1 : 0;
621
-
622
- this.node.syncManager.recordTransactionsSize([transaction], "local");
623
- this.node.syncManager.syncLocalTransaction(
624
- this.verified,
625
- transaction,
626
- sessionID,
627
- signature,
628
- txIdx,
629
- );
630
- }
611
+ txIdx,
612
+ );
631
613
 
632
- return success;
614
+ return true;
633
615
  }
634
616
 
635
617
  getCurrentContent(options?: { ignorePrivateTransactions: true }): RawCoValue {
@@ -658,6 +640,12 @@ export class CoValueCore {
658
640
  ignorePrivateTransactions: boolean;
659
641
  knownTransactions?: CoValueKnownState["sessions"];
660
642
  }): DecryptedTransaction[] {
643
+ if (!this.verified) {
644
+ throw new Error(
645
+ "CoValueCore: getValidTransactions called on coValue without verified state",
646
+ );
647
+ }
648
+
661
649
  const validTransactions = determineValidTransactions(
662
650
  this,
663
651
  options?.knownTransactions,
@@ -701,25 +689,12 @@ export class CoValueCore {
701
689
  let decryptedChanges = this._decryptionCache[tx.encryptedChanges];
702
690
 
703
691
  if (!decryptedChanges) {
704
- const decryptedString = this.crypto.decryptRawForTransaction(
705
- tx.encryptedChanges,
692
+ decryptedChanges = this.verified.decryptTransaction(
693
+ txID.sessionID,
694
+ txID.txIndex,
706
695
  readKey,
707
- {
708
- in: this.id,
709
- tx: txID,
710
- },
711
696
  );
712
697
 
713
- try {
714
- decryptedChanges = decryptedString && parseJSON(decryptedString);
715
- } catch (e) {
716
- logger.error("Failed to parse private transaction on " + this.id, {
717
- err: e,
718
- txID,
719
- changes: decryptedString?.slice(0, 50),
720
- });
721
- continue;
722
- }
723
698
  this._decryptionCache[tx.encryptedChanges] = decryptedChanges;
724
699
  }
725
700