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.
- package/.turbo/turbo-build.log +12 -0
- package/CHANGELOG.md +14 -0
- package/dist/native/coValueCore.js +4 -4
- package/dist/native/coValueCore.js.map +1 -1
- package/dist/native/coValues/account.js +3 -0
- package/dist/native/coValues/account.js.map +1 -1
- package/dist/native/exports.js +5 -2
- package/dist/native/exports.js.map +1 -1
- package/dist/native/permissions.js +30 -20
- package/dist/native/permissions.js.map +1 -1
- package/dist/web/coValueCore.js +4 -4
- package/dist/web/coValueCore.js.map +1 -1
- package/dist/web/coValues/account.js +3 -0
- package/dist/web/coValues/account.js.map +1 -1
- package/dist/web/exports.js +5 -2
- package/dist/web/exports.js.map +1 -1
- package/dist/web/permissions.js +30 -20
- package/dist/web/permissions.js.map +1 -1
- package/package.json +1 -1
- package/src/coValueCore.ts +6 -2
- package/src/coValues/account.ts +5 -0
- package/src/exports.ts +11 -2
- package/src/permissions.ts +38 -20
- package/src/tests/account.test.ts +14 -3
- package/src/tests/crypto.test.ts +149 -140
- package/src/tests/group.test.ts +36 -0
package/src/tests/crypto.test.ts
CHANGED
|
@@ -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
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
34
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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(
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
});
|
|
96
|
+
expect(crypto.shortHash({ b: "world", a: "hello" })).toEqual(
|
|
97
|
+
crypto.shortHash({ a: "hello", b: "world" }),
|
|
98
|
+
);
|
|
99
|
+
});
|
|
95
100
|
|
|
96
|
-
test(
|
|
97
|
-
|
|
101
|
+
test(`Encryption for transactions round-trips [${name}]`, () => {
|
|
102
|
+
const { secret } = crypto.newRandomKeySecret();
|
|
98
103
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
158
|
+
const keys = {
|
|
159
|
+
toEncrypt,
|
|
160
|
+
encrypting,
|
|
161
|
+
};
|
|
157
162
|
|
|
158
|
-
|
|
163
|
+
const encrypted = crypto.encryptKeySecret(keys);
|
|
159
164
|
|
|
160
|
-
|
|
165
|
+
const decrypted = crypto.decryptKeySecret(encrypted, encrypting.secret);
|
|
161
166
|
|
|
162
|
-
|
|
163
|
-
});
|
|
167
|
+
expect(decrypted).toEqual(toEncrypt.secret);
|
|
168
|
+
});
|
|
164
169
|
|
|
165
|
-
test(
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
175
|
+
const keys = {
|
|
176
|
+
toEncrypt,
|
|
177
|
+
encrypting,
|
|
178
|
+
};
|
|
174
179
|
|
|
175
|
-
|
|
180
|
+
const encrypted = crypto.encryptKeySecret(keys);
|
|
176
181
|
|
|
177
|
-
|
|
182
|
+
const decrypted = crypto.decryptKeySecret(
|
|
183
|
+
encrypted,
|
|
184
|
+
encryptingWrong.secret,
|
|
185
|
+
);
|
|
178
186
|
|
|
179
|
-
|
|
187
|
+
expect(decrypted).toBeUndefined();
|
|
188
|
+
});
|
|
180
189
|
});
|
package/src/tests/group.test.ts
CHANGED
|
@@ -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
|
});
|