cojson 0.8.41 → 0.8.45

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.
@@ -3,178 +3,187 @@ import { x25519 } from "@noble/curves/ed25519";
3
3
  import { blake3 } from "@noble/hashes/blake3";
4
4
  import { base58, base64url } from "@scure/base";
5
5
  import { expect, test } from "vitest";
6
+ import { PureJSCrypto } from "../crypto/PureJSCrypto.js";
6
7
  import { WasmCrypto } from "../crypto/WasmCrypto.js";
7
8
  import { SessionID } from "../ids.js";
8
9
  import { stableStringify } from "../jsonStringify.js";
9
10
 
10
- const Crypto = await WasmCrypto.create();
11
-
12
- test("Signatures round-trip and use stable stringify", () => {
13
- const data = { b: "world", a: "hello" };
14
- const signer = Crypto.newRandomSigner();
15
- const signature = Crypto.sign(signer, data);
16
-
17
- expect(signature).toMatch(/^signature_z/);
18
- expect(
19
- Crypto.verify(
20
- signature,
21
- { a: "hello", b: "world" },
22
- Crypto.getSignerID(signer),
23
- ),
24
- ).toBe(true);
25
- });
26
-
27
- test("Invalid signatures don't verify", () => {
28
- const data = { b: "world", a: "hello" };
29
- const signer = Crypto.newRandomSigner();
30
- const signer2 = Crypto.newRandomSigner();
31
- const wrongSignature = Crypto.sign(signer2, data);
11
+ const wasmCrypto = await WasmCrypto.create();
12
+ const pureJSCrypto = await PureJSCrypto.create();
13
+
14
+ [wasmCrypto, pureJSCrypto].forEach((crypto) => {
15
+ const name = crypto.constructor.name;
16
+
17
+ test(`Signatures round-trip and use stable stringify [${name}]`, () => {
18
+ const data = { b: "world", a: "hello" };
19
+ const signer = crypto.newRandomSigner();
20
+ const signature = crypto.sign(signer, data);
21
+
22
+ expect(signature).toMatch(/^signature_z/);
23
+ expect(
24
+ crypto.verify(
25
+ signature,
26
+ { a: "hello", b: "world" },
27
+ crypto.getSignerID(signer),
28
+ ),
29
+ ).toBe(true);
30
+ });
32
31
 
33
- expect(Crypto.verify(wrongSignature, data, Crypto.getSignerID(signer))).toBe(
34
- false,
35
- );
36
- });
32
+ test(`Invalid signatures don't verify [${name}]`, () => {
33
+ const data = { b: "world", a: "hello" };
34
+ const signer = crypto.newRandomSigner();
35
+ const signer2 = crypto.newRandomSigner();
36
+ const wrongSignature = crypto.sign(signer2, data);
37
37
 
38
- test("encrypting round-trips, but invalid receiver can't unseal", () => {
39
- const data = { b: "world", a: "hello" };
40
- const sender = Crypto.newRandomSealer();
41
- const sealer = Crypto.newRandomSealer();
42
- const wrongSealer = Crypto.newRandomSealer();
43
-
44
- const nOnceMaterial = {
45
- in: "co_zTEST",
46
- tx: { sessionID: "co_zTEST_session_zTEST" as SessionID, txIndex: 0 },
47
- } as const;
48
-
49
- const sealed = Crypto.seal({
50
- message: data,
51
- from: sender,
52
- to: Crypto.getSealerID(sealer),
53
- nOnceMaterial,
38
+ expect(
39
+ crypto.verify(wrongSignature, data, crypto.getSignerID(signer)),
40
+ ).toBe(false);
54
41
  });
55
42
 
56
- expect(
57
- Crypto.unseal(sealed, sealer, Crypto.getSealerID(sender), nOnceMaterial),
58
- ).toEqual(data);
59
- expect(() =>
60
- Crypto.unseal(
61
- sealed,
62
- wrongSealer,
63
- Crypto.getSealerID(sender),
43
+ test(`encrypting round-trips, but invalid receiver can't unseal [${name}]`, () => {
44
+ const data = { b: "world", a: "hello" };
45
+ const sender = crypto.newRandomSealer();
46
+ const sealer = crypto.newRandomSealer();
47
+ const wrongSealer = crypto.newRandomSealer();
48
+
49
+ const nOnceMaterial = {
50
+ in: "co_zTEST",
51
+ tx: { sessionID: "co_zTEST_session_zTEST" as SessionID, txIndex: 0 },
52
+ } as const;
53
+
54
+ const sealed = crypto.seal({
55
+ message: data,
56
+ from: sender,
57
+ to: crypto.getSealerID(sealer),
64
58
  nOnceMaterial,
65
- ),
66
- ).toThrow(/Wrong tag/);
67
-
68
- // trying with wrong sealer secret, by hand
69
- const nOnce = blake3(
70
- new TextEncoder().encode(stableStringify(nOnceMaterial)),
71
- ).slice(0, 24);
72
- const sealer3priv = base58.decode(
73
- wrongSealer.substring("sealerSecret_z".length),
74
- );
75
- const senderPub = base58.decode(
76
- Crypto.getSealerID(sender).substring("sealer_z".length),
77
- );
78
- const sealedBytes = base64url.decode(sealed.substring("sealed_U".length));
79
- const sharedSecret = x25519.getSharedSecret(sealer3priv, senderPub);
80
-
81
- expect(() => {
82
- const _ = xsalsa20_poly1305(sharedSecret, nOnce).decrypt(sealedBytes);
83
- }).toThrow("Wrong tag");
84
- });
59
+ });
60
+
61
+ expect(
62
+ crypto.unseal(sealed, sealer, crypto.getSealerID(sender), nOnceMaterial),
63
+ ).toEqual(data);
64
+ expect(() =>
65
+ crypto.unseal(
66
+ sealed,
67
+ wrongSealer,
68
+ crypto.getSealerID(sender),
69
+ nOnceMaterial,
70
+ ),
71
+ ).toThrow(/Wrong tag/);
72
+
73
+ // trying with wrong sealer secret, by hand
74
+ const nOnce = blake3(
75
+ new TextEncoder().encode(stableStringify(nOnceMaterial)),
76
+ ).slice(0, 24);
77
+ const sealer3priv = base58.decode(
78
+ wrongSealer.substring("sealerSecret_z".length),
79
+ );
80
+ const senderPub = base58.decode(
81
+ crypto.getSealerID(sender).substring("sealer_z".length),
82
+ );
83
+ const sealedBytes = base64url.decode(sealed.substring("sealed_U".length));
84
+ const sharedSecret = x25519.getSharedSecret(sealer3priv, senderPub);
85
+
86
+ expect(() => {
87
+ const _ = xsalsa20_poly1305(sharedSecret, nOnce).decrypt(sealedBytes);
88
+ }).toThrow("Wrong tag");
89
+ });
85
90
 
86
- test("Hashing is deterministic", () => {
87
- expect(Crypto.secureHash({ b: "world", a: "hello" })).toEqual(
88
- Crypto.secureHash({ a: "hello", b: "world" }),
89
- );
91
+ test(`Hashing is deterministic [${name}]`, () => {
92
+ expect(crypto.secureHash({ b: "world", a: "hello" })).toEqual(
93
+ crypto.secureHash({ a: "hello", b: "world" }),
94
+ );
90
95
 
91
- expect(Crypto.shortHash({ b: "world", a: "hello" })).toEqual(
92
- Crypto.shortHash({ a: "hello", b: "world" }),
93
- );
94
- });
96
+ expect(crypto.shortHash({ b: "world", a: "hello" })).toEqual(
97
+ crypto.shortHash({ a: "hello", b: "world" }),
98
+ );
99
+ });
95
100
 
96
- test("Encryption for transactions round-trips", () => {
97
- const { secret } = Crypto.newRandomKeySecret();
101
+ test(`Encryption for transactions round-trips [${name}]`, () => {
102
+ const { secret } = crypto.newRandomKeySecret();
98
103
 
99
- const encrypted1 = Crypto.encryptForTransaction({ a: "hello" }, secret, {
100
- in: "co_zTEST",
101
- tx: { sessionID: "co_zTEST_session_zTEST" as SessionID, txIndex: 0 },
102
- });
104
+ const encrypted1 = crypto.encryptForTransaction({ a: "hello" }, secret, {
105
+ in: "co_zTEST",
106
+ tx: { sessionID: "co_zTEST_session_zTEST" as SessionID, txIndex: 0 },
107
+ });
103
108
 
104
- const encrypted2 = Crypto.encryptForTransaction({ b: "world" }, secret, {
105
- in: "co_zTEST",
106
- tx: { sessionID: "co_zTEST_session_zTEST" as SessionID, txIndex: 1 },
107
- });
109
+ const encrypted2 = crypto.encryptForTransaction({ b: "world" }, secret, {
110
+ in: "co_zTEST",
111
+ tx: { sessionID: "co_zTEST_session_zTEST" as SessionID, txIndex: 1 },
112
+ });
108
113
 
109
- const decrypted1 = Crypto.decryptForTransaction(encrypted1, secret, {
110
- in: "co_zTEST",
111
- tx: { sessionID: "co_zTEST_session_zTEST" as SessionID, txIndex: 0 },
112
- });
114
+ const decrypted1 = crypto.decryptForTransaction(encrypted1, secret, {
115
+ in: "co_zTEST",
116
+ tx: { sessionID: "co_zTEST_session_zTEST" as SessionID, txIndex: 0 },
117
+ });
113
118
 
114
- const decrypted2 = Crypto.decryptForTransaction(encrypted2, secret, {
115
- in: "co_zTEST",
116
- tx: { sessionID: "co_zTEST_session_zTEST" as SessionID, txIndex: 1 },
119
+ const decrypted2 = crypto.decryptForTransaction(encrypted2, secret, {
120
+ in: "co_zTEST",
121
+ tx: { sessionID: "co_zTEST_session_zTEST" as SessionID, txIndex: 1 },
122
+ });
123
+
124
+ expect([decrypted1, decrypted2]).toEqual([{ a: "hello" }, { b: "world" }]);
117
125
  });
118
126
 
119
- expect([decrypted1, decrypted2]).toEqual([{ a: "hello" }, { b: "world" }]);
120
- });
127
+ test(`Encryption for transactions doesn't decrypt with a wrong key [${name}]`, () => {
128
+ const { secret } = crypto.newRandomKeySecret();
129
+ const { secret: secret2 } = crypto.newRandomKeySecret();
121
130
 
122
- test("Encryption for transactions doesn't decrypt with a wrong key", () => {
123
- const { secret } = Crypto.newRandomKeySecret();
124
- const { secret: secret2 } = Crypto.newRandomKeySecret();
131
+ const encrypted1 = crypto.encryptForTransaction({ a: "hello" }, secret, {
132
+ in: "co_zTEST",
133
+ tx: { sessionID: "co_zTEST_session_zTEST" as SessionID, txIndex: 0 },
134
+ });
125
135
 
126
- const encrypted1 = Crypto.encryptForTransaction({ a: "hello" }, secret, {
127
- in: "co_zTEST",
128
- tx: { sessionID: "co_zTEST_session_zTEST" as SessionID, txIndex: 0 },
129
- });
136
+ const encrypted2 = crypto.encryptForTransaction({ b: "world" }, secret, {
137
+ in: "co_zTEST",
138
+ tx: { sessionID: "co_zTEST_session_zTEST" as SessionID, txIndex: 1 },
139
+ });
130
140
 
131
- const encrypted2 = Crypto.encryptForTransaction({ b: "world" }, secret, {
132
- in: "co_zTEST",
133
- tx: { sessionID: "co_zTEST_session_zTEST" as SessionID, txIndex: 1 },
134
- });
141
+ const decrypted1 = crypto.decryptForTransaction(encrypted1, secret2, {
142
+ in: "co_zTEST",
143
+ tx: { sessionID: "co_zTEST_session_zTEST" as SessionID, txIndex: 0 },
144
+ });
135
145
 
136
- const decrypted1 = Crypto.decryptForTransaction(encrypted1, secret2, {
137
- in: "co_zTEST",
138
- tx: { sessionID: "co_zTEST_session_zTEST" as SessionID, txIndex: 0 },
139
- });
146
+ const decrypted2 = crypto.decryptForTransaction(encrypted2, secret2, {
147
+ in: "co_zTEST",
148
+ tx: { sessionID: "co_zTEST_session_zTEST" as SessionID, txIndex: 1 },
149
+ });
140
150
 
141
- const decrypted2 = Crypto.decryptForTransaction(encrypted2, secret2, {
142
- in: "co_zTEST",
143
- tx: { sessionID: "co_zTEST_session_zTEST" as SessionID, txIndex: 1 },
151
+ expect([decrypted1, decrypted2]).toEqual([undefined, undefined]);
144
152
  });
145
153
 
146
- expect([decrypted1, decrypted2]).toEqual([undefined, undefined]);
147
- });
148
-
149
- test("Encryption of keySecrets round-trips", () => {
150
- const toEncrypt = Crypto.newRandomKeySecret();
151
- const encrypting = Crypto.newRandomKeySecret();
154
+ test(`Encryption of keySecrets round-trips [${name}]`, () => {
155
+ const toEncrypt = crypto.newRandomKeySecret();
156
+ const encrypting = crypto.newRandomKeySecret();
152
157
 
153
- const keys = {
154
- toEncrypt,
155
- encrypting,
156
- };
158
+ const keys = {
159
+ toEncrypt,
160
+ encrypting,
161
+ };
157
162
 
158
- const encrypted = Crypto.encryptKeySecret(keys);
163
+ const encrypted = crypto.encryptKeySecret(keys);
159
164
 
160
- const decrypted = Crypto.decryptKeySecret(encrypted, encrypting.secret);
165
+ const decrypted = crypto.decryptKeySecret(encrypted, encrypting.secret);
161
166
 
162
- expect(decrypted).toEqual(toEncrypt.secret);
163
- });
167
+ expect(decrypted).toEqual(toEncrypt.secret);
168
+ });
164
169
 
165
- test("Encryption of keySecrets doesn't decrypt with a wrong key", () => {
166
- const toEncrypt = Crypto.newRandomKeySecret();
167
- const encrypting = Crypto.newRandomKeySecret();
168
- const encryptingWrong = Crypto.newRandomKeySecret();
170
+ test(`Encryption of keySecrets doesn't decrypt with a wrong key [${name}]`, () => {
171
+ const toEncrypt = crypto.newRandomKeySecret();
172
+ const encrypting = crypto.newRandomKeySecret();
173
+ const encryptingWrong = crypto.newRandomKeySecret();
169
174
 
170
- const keys = {
171
- toEncrypt,
172
- encrypting,
173
- };
175
+ const keys = {
176
+ toEncrypt,
177
+ encrypting,
178
+ };
174
179
 
175
- const encrypted = Crypto.encryptKeySecret(keys);
180
+ const encrypted = crypto.encryptKeySecret(keys);
176
181
 
177
- const decrypted = Crypto.decryptKeySecret(encrypted, encryptingWrong.secret);
182
+ const decrypted = crypto.decryptKeySecret(
183
+ encrypted,
184
+ encryptingWrong.secret,
185
+ );
178
186
 
179
- expect(decrypted).toBeUndefined();
187
+ expect(decrypted).toBeUndefined();
188
+ });
180
189
  });
@@ -1,4 +1,5 @@
1
1
  import { describe, expect, test } from "vitest";
2
+ import { CoValueState } from "../coValueState.js";
2
3
  import { RawCoList } from "../coValues/coList.js";
3
4
  import { RawCoMap } from "../coValues/coMap.js";
4
5
  import { RawCoStream } from "../coValues/coStream.js";
@@ -492,4 +493,39 @@ describe("writeOnly", () => {
492
493
  // The writer role should be able to see the edits from the admin
493
494
  expect(mapOnNode2.get("test")).toEqual("Written from the admin");
494
495
  });
496
+
497
+ test("upgrade to writer roles should work correctly", async () => {
498
+ const { node1, node2 } = await createTwoConnectedNodes("server", "server");
499
+
500
+ const group = node1.node.createGroup();
501
+ group.addMember(
502
+ await loadCoValueOrFail(node1.node, node2.accountID),
503
+ "writeOnly",
504
+ );
505
+
506
+ await group.core.waitForSync();
507
+
508
+ const groupOnNode2 = await loadCoValueOrFail(node2.node, group.id);
509
+ const map = groupOnNode2.createMap();
510
+ map.set("test", "Written from the writeOnly member");
511
+
512
+ await map.core.waitForSync();
513
+
514
+ group.addMember(
515
+ await loadCoValueOrFail(node1.node, node2.accountID),
516
+ "writer",
517
+ );
518
+
519
+ group.core.waitForSync();
520
+
521
+ node2.node.coValuesStore.coValues.delete(map.id);
522
+ expect(node2.node.coValuesStore.get(map.id)).toEqual(
523
+ CoValueState.Unknown(map.id),
524
+ );
525
+
526
+ const mapOnNode2 = await loadCoValueOrFail(node2.node, map.id);
527
+
528
+ // The writer role should be able to see the edits from the admin
529
+ expect(mapOnNode2.get("test")).toEqual("Written from the writeOnly member");
530
+ });
495
531
  });