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.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +18 -0
- package/dist/coValueCore/SessionMap.d.ts +45 -0
- package/dist/coValueCore/SessionMap.d.ts.map +1 -0
- package/dist/coValueCore/SessionMap.js +118 -0
- package/dist/coValueCore/SessionMap.js.map +1 -0
- package/dist/coValueCore/coValueCore.d.ts +10 -4
- package/dist/coValueCore/coValueCore.d.ts.map +1 -1
- package/dist/coValueCore/coValueCore.js +55 -68
- package/dist/coValueCore/coValueCore.js.map +1 -1
- package/dist/coValueCore/verifiedState.d.ts +15 -19
- package/dist/coValueCore/verifiedState.d.ts.map +1 -1
- package/dist/coValueCore/verifiedState.js +24 -87
- package/dist/coValueCore/verifiedState.js.map +1 -1
- package/dist/coValues/account.d.ts +4 -0
- package/dist/coValues/account.d.ts.map +1 -1
- package/dist/coValues/account.js +24 -4
- package/dist/coValues/account.js.map +1 -1
- package/dist/coValues/group.d.ts.map +1 -1
- package/dist/coValues/group.js +6 -2
- package/dist/coValues/group.js.map +1 -1
- package/dist/crypto/PureJSCrypto.d.ts +31 -3
- package/dist/crypto/PureJSCrypto.d.ts.map +1 -1
- package/dist/crypto/PureJSCrypto.js +115 -0
- package/dist/crypto/PureJSCrypto.js.map +1 -1
- package/dist/crypto/WasmCrypto.d.ts +23 -4
- package/dist/crypto/WasmCrypto.d.ts.map +1 -1
- package/dist/crypto/WasmCrypto.js +44 -2
- package/dist/crypto/WasmCrypto.js.map +1 -1
- package/dist/crypto/crypto.d.ts +17 -1
- package/dist/crypto/crypto.d.ts.map +1 -1
- package/dist/crypto/crypto.js.map +1 -1
- package/dist/localNode.d.ts +1 -0
- package/dist/localNode.d.ts.map +1 -1
- package/dist/localNode.js +10 -5
- package/dist/localNode.js.map +1 -1
- package/dist/permissions.d.ts +17 -1
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js.map +1 -1
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +55 -49
- package/dist/sync.js.map +1 -1
- package/dist/tests/PureJSCrypto.test.d.ts +2 -0
- package/dist/tests/PureJSCrypto.test.d.ts.map +1 -0
- package/dist/tests/PureJSCrypto.test.js +102 -0
- package/dist/tests/PureJSCrypto.test.js.map +1 -0
- package/dist/tests/WasmCrypto.test.d.ts +2 -0
- package/dist/tests/WasmCrypto.test.d.ts.map +1 -0
- package/dist/tests/WasmCrypto.test.js +88 -0
- package/dist/tests/WasmCrypto.test.js.map +1 -0
- package/dist/tests/coValueCore.test.js +62 -187
- package/dist/tests/coValueCore.test.js.map +1 -1
- package/dist/tests/coreWasm.test.d.ts +2 -0
- package/dist/tests/coreWasm.test.d.ts.map +1 -0
- package/dist/tests/coreWasm.test.js +80 -0
- package/dist/tests/coreWasm.test.js.map +1 -0
- package/dist/tests/group.addMember.test.js +6 -11
- package/dist/tests/group.addMember.test.js.map +1 -1
- package/dist/tests/sync.load.test.js +40 -1
- package/dist/tests/sync.load.test.js.map +1 -1
- package/dist/tests/sync.test.js +1 -1
- package/dist/tests/sync.test.js.map +1 -1
- package/dist/tests/testUtils.d.ts +3 -0
- package/dist/tests/testUtils.d.ts.map +1 -1
- package/dist/tests/testUtils.js +4 -1
- package/dist/tests/testUtils.js.map +1 -1
- package/package.json +3 -3
- package/src/coValueCore/SessionMap.ts +229 -0
- package/src/coValueCore/coValueCore.ts +106 -121
- package/src/coValueCore/verifiedState.ts +61 -132
- package/src/coValues/account.ts +28 -4
- package/src/coValues/group.ts +10 -2
- package/src/crypto/PureJSCrypto.ts +206 -2
- package/src/crypto/WasmCrypto.ts +95 -4
- package/src/crypto/crypto.ts +38 -1
- package/src/localNode.ts +18 -10
- package/src/permissions.ts +17 -1
- package/src/sync.ts +63 -59
- package/src/tests/PureJSCrypto.test.ts +153 -0
- package/src/tests/WasmCrypto.test.ts +128 -0
- package/src/tests/coValueCore.test.ts +81 -293
- package/src/tests/coreWasm.test.ts +142 -0
- package/src/tests/group.addMember.test.ts +69 -63
- package/src/tests/sync.load.test.ts +52 -0
- package/src/tests/sync.test.ts +0 -2
- package/src/tests/testUtils.ts +9 -1
package/src/crypto/WasmCrypto.ts
CHANGED
|
@@ -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 "
|
|
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
|
|
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
|
}
|
package/src/crypto/crypto.ts
CHANGED
|
@@ -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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
|
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
|
|
371
|
+
new VerifiedState(id, this.crypto, header),
|
|
364
372
|
);
|
|
365
373
|
|
|
366
374
|
this.garbageCollector?.trackCoValueAccess(coValue);
|
package/src/permissions.ts
CHANGED
|
@@ -31,7 +31,23 @@ export type PermissionsDef =
|
|
|
31
31
|
| { type: "ownedByGroup"; group: RawCoID }
|
|
32
32
|
| { type: "unsafeAllowAll" };
|
|
33
33
|
|
|
34
|
-
export type AccountRole =
|
|
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
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
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
|
-
|
|
523
|
-
coValue.markMissingDependency(dependency);
|
|
527
|
+
const peers = this.getServerPeers();
|
|
524
528
|
|
|
525
|
-
|
|
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
|
-
|
|
528
|
-
|
|
529
|
-
|
|
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
|
-
|
|
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
|
-
|
|
598
|
-
|
|
602
|
+
if (isAccountID(accountId)) {
|
|
603
|
+
const account = this.local.getCoValue(accountId);
|
|
599
604
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
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
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
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
|
-
|
|
614
|
-
|
|
618
|
+
account.load(peers);
|
|
619
|
+
}
|
|
615
620
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
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
|
-
|
|
635
|
-
|
|
636
|
-
|
|
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
|
+
});
|