cojson 0.7.0-alpha.36 → 0.7.0-alpha.38

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 (106) hide show
  1. package/.eslintrc.cjs +3 -2
  2. package/.prettierrc.js +9 -0
  3. package/.turbo/turbo-build.log +3 -36
  4. package/.turbo/turbo-lint.log +4 -0
  5. package/.turbo/turbo-test.log +1106 -0
  6. package/CHANGELOG.md +12 -0
  7. package/README.md +3 -1
  8. package/dist/base64url.test.js +25 -0
  9. package/dist/base64url.test.js.map +1 -0
  10. package/dist/coValueCore.js +16 -15
  11. package/dist/coValueCore.js.map +1 -1
  12. package/dist/coValues/account.js +16 -15
  13. package/dist/coValues/account.js.map +1 -1
  14. package/dist/coValues/group.js +13 -14
  15. package/dist/coValues/group.js.map +1 -1
  16. package/dist/coreToCoValue.js.map +1 -1
  17. package/dist/crypto/PureJSCrypto.js +89 -0
  18. package/dist/crypto/PureJSCrypto.js.map +1 -0
  19. package/dist/crypto/WasmCrypto.js +127 -0
  20. package/dist/crypto/WasmCrypto.js.map +1 -0
  21. package/dist/crypto/crypto.js +151 -0
  22. package/dist/crypto/crypto.js.map +1 -0
  23. package/dist/ids.js +4 -2
  24. package/dist/ids.js.map +1 -1
  25. package/dist/index.js +5 -9
  26. package/dist/index.js.map +1 -1
  27. package/dist/jsonStringify.js.map +1 -1
  28. package/dist/localNode.js +24 -24
  29. package/dist/localNode.js.map +1 -1
  30. package/dist/permissions.js.map +1 -1
  31. package/dist/storage/FileSystem.js +2 -2
  32. package/dist/storage/FileSystem.js.map +1 -1
  33. package/dist/storage/chunksAndKnownStates.js +2 -2
  34. package/dist/storage/chunksAndKnownStates.js.map +1 -1
  35. package/dist/storage/index.js.map +1 -1
  36. package/dist/sync.js +6 -2
  37. package/dist/sync.js.map +1 -1
  38. package/dist/tests/account.test.js +58 -0
  39. package/dist/tests/account.test.js.map +1 -0
  40. package/dist/tests/coList.test.js +76 -0
  41. package/dist/tests/coList.test.js.map +1 -0
  42. package/dist/tests/coMap.test.js +136 -0
  43. package/dist/tests/coMap.test.js.map +1 -0
  44. package/dist/tests/coStream.test.js +172 -0
  45. package/dist/tests/coStream.test.js.map +1 -0
  46. package/dist/tests/coValueCore.test.js +114 -0
  47. package/dist/tests/coValueCore.test.js.map +1 -0
  48. package/dist/tests/crypto.test.js +118 -0
  49. package/dist/tests/crypto.test.js.map +1 -0
  50. package/dist/tests/cryptoImpl.test.js +113 -0
  51. package/dist/tests/cryptoImpl.test.js.map +1 -0
  52. package/dist/tests/group.test.js +34 -0
  53. package/dist/tests/group.test.js.map +1 -0
  54. package/dist/tests/permissions.test.js +1060 -0
  55. package/dist/tests/permissions.test.js.map +1 -0
  56. package/dist/tests/sync.test.js +816 -0
  57. package/dist/tests/sync.test.js.map +1 -0
  58. package/dist/tests/testUtils.js +10 -9
  59. package/dist/tests/testUtils.js.map +1 -1
  60. package/dist/typeUtils/accountOrAgentIDfromSessionID.js.map +1 -1
  61. package/dist/typeUtils/isAccountID.js.map +1 -1
  62. package/dist/typeUtils/isCoValue.js.map +1 -1
  63. package/package.json +14 -28
  64. package/src/base64url.test.ts +6 -6
  65. package/src/coValue.ts +1 -1
  66. package/src/coValueCore.ts +87 -85
  67. package/src/coValues/account.ts +26 -28
  68. package/src/coValues/coList.ts +10 -10
  69. package/src/coValues/coMap.ts +10 -10
  70. package/src/coValues/coStream.ts +17 -17
  71. package/src/coValues/group.ts +93 -109
  72. package/src/coreToCoValue.ts +5 -2
  73. package/src/crypto/PureJSCrypto.ts +200 -0
  74. package/src/crypto/WasmCrypto.ts +259 -0
  75. package/src/crypto/crypto.ts +336 -0
  76. package/src/ids.ts +8 -7
  77. package/src/index.ts +14 -26
  78. package/src/jsonStringify.ts +6 -4
  79. package/src/jsonValue.ts +2 -2
  80. package/src/localNode.ts +86 -80
  81. package/src/media.ts +3 -3
  82. package/src/permissions.ts +14 -16
  83. package/src/storage/FileSystem.ts +31 -30
  84. package/src/storage/chunksAndKnownStates.ts +24 -17
  85. package/src/storage/index.ts +42 -38
  86. package/src/streamUtils.ts +12 -12
  87. package/src/sync.ts +56 -40
  88. package/src/tests/account.test.ts +8 -12
  89. package/src/tests/coList.test.ts +19 -25
  90. package/src/tests/coMap.test.ts +25 -30
  91. package/src/tests/coStream.test.ts +28 -38
  92. package/src/tests/coValueCore.test.ts +35 -36
  93. package/src/tests/crypto.test.ts +66 -72
  94. package/src/tests/cryptoImpl.test.ts +183 -0
  95. package/src/tests/group.test.ts +16 -17
  96. package/src/tests/permissions.test.ts +237 -254
  97. package/src/tests/sync.test.ts +119 -120
  98. package/src/tests/testUtils.ts +22 -19
  99. package/src/typeUtils/accountOrAgentIDfromSessionID.ts +1 -2
  100. package/src/typeUtils/expectGroup.ts +1 -1
  101. package/src/typeUtils/isAccountID.ts +0 -1
  102. package/src/typeUtils/isCoValue.ts +1 -2
  103. package/tsconfig.json +0 -1
  104. package/dist/crypto.js +0 -255
  105. package/dist/crypto.js.map +0 -1
  106. package/src/crypto.ts +0 -485
@@ -0,0 +1,200 @@
1
+ import { ed25519, x25519 } from "@noble/curves/ed25519";
2
+ import { xsalsa20_poly1305, xsalsa20 } from "@noble/ciphers/salsa";
3
+ import { JsonValue } from "../jsonValue.js";
4
+ import { base58 } from "@scure/base";
5
+ import { randomBytes } from "@noble/ciphers/webcrypto/utils";
6
+ import { RawCoID, TransactionID } from "../ids.js";
7
+ import { base64URLtoBytes, bytesToBase64url } from "../base64url.js";
8
+ import { Stringified, stableStringify } from "../jsonStringify.js";
9
+ import { blake3 } from "@noble/hashes/blake3";
10
+ import {
11
+ CryptoProvider,
12
+ SignerSecret,
13
+ SignerID,
14
+ Signature,
15
+ textEncoder,
16
+ SealerSecret,
17
+ SealerID,
18
+ KeySecret,
19
+ Encrypted,
20
+ textDecoder,
21
+ Sealed,
22
+ } from "./crypto.js";
23
+
24
+ type Blake3State = ReturnType<typeof blake3.create>;
25
+
26
+ export class PureJSCrypto extends CryptoProvider<Blake3State> {
27
+ static async create(): Promise<PureJSCrypto> {
28
+ return new PureJSCrypto();
29
+ }
30
+
31
+ randomBytes(length: number): Uint8Array {
32
+ return randomBytes(length);
33
+ }
34
+
35
+ emptyBlake3State(): Blake3State {
36
+ return blake3.create({});
37
+ }
38
+
39
+ blake3HashOnce(data: Uint8Array) {
40
+ return blake3(data);
41
+ }
42
+
43
+ blake3HashOnceWithContext(
44
+ data: Uint8Array,
45
+ { context }: { context: Uint8Array },
46
+ ) {
47
+ return blake3.create({}).update(context).update(data).digest();
48
+ }
49
+
50
+ blake3IncrementalUpdate(state: Blake3State, data: Uint8Array) {
51
+ return state.update(data);
52
+ }
53
+
54
+ blake3DigestForState(state: Blake3State): Uint8Array {
55
+ return state.clone().digest();
56
+ }
57
+
58
+ newEd25519SigningKey(): Uint8Array {
59
+ return ed25519.utils.randomPrivateKey();
60
+ }
61
+
62
+ getSignerID(secret: SignerSecret): SignerID {
63
+ return `signer_z${base58.encode(
64
+ ed25519.getPublicKey(
65
+ base58.decode(secret.substring("signerSecret_z".length)),
66
+ ),
67
+ )}`;
68
+ }
69
+
70
+ sign(secret: SignerSecret, message: JsonValue): Signature {
71
+ const signature = ed25519.sign(
72
+ textEncoder.encode(stableStringify(message)),
73
+ base58.decode(secret.substring("signerSecret_z".length)),
74
+ );
75
+ return `signature_z${base58.encode(signature)}`;
76
+ }
77
+
78
+ verify(signature: Signature, message: JsonValue, id: SignerID): boolean {
79
+ return ed25519.verify(
80
+ base58.decode(signature.substring("signature_z".length)),
81
+ textEncoder.encode(stableStringify(message)),
82
+ base58.decode(id.substring("signer_z".length)),
83
+ );
84
+ }
85
+
86
+ newX25519StaticSecret(): Uint8Array {
87
+ return x25519.utils.randomPrivateKey();
88
+ }
89
+
90
+ getSealerID(secret: SealerSecret): SealerID {
91
+ return `sealer_z${base58.encode(
92
+ x25519.getPublicKey(
93
+ base58.decode(secret.substring("sealerSecret_z".length)),
94
+ ),
95
+ )}`;
96
+ }
97
+
98
+ encrypt<T extends JsonValue, N extends JsonValue>(
99
+ value: T,
100
+ keySecret: KeySecret,
101
+ nOnceMaterial: N,
102
+ ): Encrypted<T, N> {
103
+ const keySecretBytes = base58.decode(
104
+ keySecret.substring("keySecret_z".length),
105
+ );
106
+ const nOnce = this.blake3HashOnce(
107
+ textEncoder.encode(stableStringify(nOnceMaterial)),
108
+ ).slice(0, 24);
109
+
110
+ const plaintext = textEncoder.encode(stableStringify(value));
111
+ const ciphertext = xsalsa20(keySecretBytes, nOnce, plaintext);
112
+ return `encrypted_U${bytesToBase64url(ciphertext)}` as Encrypted<T, N>;
113
+ }
114
+
115
+ decryptRaw<T extends JsonValue, N extends JsonValue>(
116
+ encrypted: Encrypted<T, N>,
117
+ keySecret: KeySecret,
118
+ nOnceMaterial: N,
119
+ ): Stringified<T> {
120
+ const keySecretBytes = base58.decode(
121
+ keySecret.substring("keySecret_z".length),
122
+ );
123
+ const nOnce = this.blake3HashOnce(
124
+ textEncoder.encode(stableStringify(nOnceMaterial)),
125
+ ).slice(0, 24);
126
+
127
+ const ciphertext = base64URLtoBytes(
128
+ encrypted.substring("encrypted_U".length),
129
+ );
130
+ const plaintext = xsalsa20(keySecretBytes, nOnce, ciphertext);
131
+
132
+ return textDecoder.decode(plaintext) as Stringified<T>;
133
+ }
134
+
135
+ seal<T extends JsonValue>({
136
+ message,
137
+ from,
138
+ to,
139
+ nOnceMaterial,
140
+ }: {
141
+ message: T;
142
+ from: SealerSecret;
143
+ to: SealerID;
144
+ nOnceMaterial: { in: RawCoID; tx: TransactionID };
145
+ }): Sealed<T> {
146
+ const nOnce = this.blake3HashOnce(
147
+ textEncoder.encode(stableStringify(nOnceMaterial)),
148
+ ).slice(0, 24);
149
+
150
+ const sealerPub = base58.decode(to.substring("sealer_z".length));
151
+
152
+ const senderPriv = base58.decode(
153
+ from.substring("sealerSecret_z".length),
154
+ );
155
+
156
+ const plaintext = textEncoder.encode(stableStringify(message));
157
+
158
+ const sharedSecret = x25519.getSharedSecret(senderPriv, sealerPub);
159
+
160
+ const sealedBytes = xsalsa20_poly1305(sharedSecret, nOnce).encrypt(
161
+ plaintext,
162
+ );
163
+
164
+ return `sealed_U${bytesToBase64url(sealedBytes)}` as Sealed<T>;
165
+ }
166
+
167
+ unseal<T extends JsonValue>(
168
+ sealed: Sealed<T>,
169
+ sealer: SealerSecret,
170
+ from: SealerID,
171
+ nOnceMaterial: { in: RawCoID; tx: TransactionID },
172
+ ): T | undefined {
173
+ const nOnce = this.blake3HashOnce(
174
+ textEncoder.encode(stableStringify(nOnceMaterial)),
175
+ ).slice(0, 24);
176
+
177
+ const sealerPriv = base58.decode(
178
+ sealer.substring("sealerSecret_z".length),
179
+ );
180
+
181
+ const senderPub = base58.decode(from.substring("sealer_z".length));
182
+
183
+ const sealedBytes = base64URLtoBytes(
184
+ sealed.substring("sealed_U".length),
185
+ );
186
+
187
+ const sharedSecret = x25519.getSharedSecret(sealerPriv, senderPub);
188
+
189
+ const plaintext = xsalsa20_poly1305(sharedSecret, nOnce).decrypt(
190
+ sealedBytes,
191
+ );
192
+
193
+ try {
194
+ return JSON.parse(textDecoder.decode(plaintext));
195
+ } catch (e) {
196
+ console.error("Failed to decrypt/parse sealed message", e);
197
+ return undefined;
198
+ }
199
+ }
200
+ }
@@ -0,0 +1,259 @@
1
+ import {
2
+ initBundledOnce,
3
+ Ed25519SigningKey,
4
+ Ed25519VerifyingKey,
5
+ X25519StaticSecret,
6
+ Memory,
7
+ Ed25519Signature,
8
+ X25519PublicKey,
9
+ } from "@hazae41/berith";
10
+ import { xsalsa20_poly1305, xsalsa20 } from "@noble/ciphers/salsa";
11
+ import { JsonValue } from "../jsonValue.js";
12
+ import { base58 } from "@scure/base";
13
+ import { randomBytes } from "@noble/ciphers/webcrypto/utils";
14
+ import { RawCoID, TransactionID } from "../ids.js";
15
+ import { base64URLtoBytes, bytesToBase64url } from "../base64url.js";
16
+ import { createBLAKE3 } from "hash-wasm";
17
+ import { Stringified, stableStringify } from "../jsonStringify.js";
18
+ import {
19
+ CryptoProvider,
20
+ SignerSecret,
21
+ SignerID,
22
+ Signature,
23
+ textEncoder,
24
+ SealerSecret,
25
+ SealerID,
26
+ KeySecret,
27
+ Encrypted,
28
+ textDecoder,
29
+ Sealed,
30
+ } from "./crypto.js";
31
+
32
+ export class WasmCrypto extends CryptoProvider<Uint8Array> {
33
+ private constructor(
34
+ public blake3Instance: Awaited<ReturnType<typeof createBLAKE3>>,
35
+ ) {
36
+ super();
37
+ }
38
+
39
+ static async create(): Promise<WasmCrypto> {
40
+ return Promise.all([
41
+ createBLAKE3(),
42
+ initBundledOnce(),
43
+ new Promise<void>((resolve) => {
44
+ if ("crypto" in globalThis) {
45
+ resolve();
46
+ } else {
47
+ return import("node:crypto").then(({ webcrypto }) => {
48
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
49
+ (globalThis as any).crypto = webcrypto;
50
+ resolve();
51
+ });
52
+ }
53
+ }),
54
+ ]).then(([blake3instance]) => new WasmCrypto(blake3instance));
55
+ }
56
+
57
+ randomBytes(length: number): Uint8Array {
58
+ return randomBytes(length);
59
+ }
60
+
61
+ emptyBlake3State(): Uint8Array {
62
+ return this.blake3Instance.init().save();
63
+ }
64
+
65
+ blake3HashOnce(data: Uint8Array) {
66
+ return this.blake3Instance.init().update(data).digest("binary");
67
+ }
68
+
69
+ blake3HashOnceWithContext(
70
+ data: Uint8Array,
71
+ { context }: { context: Uint8Array },
72
+ ) {
73
+ return this.blake3Instance
74
+ .init()
75
+ .update(context)
76
+ .update(data)
77
+ .digest("binary");
78
+ }
79
+
80
+ blake3IncrementalUpdate(state: Uint8Array, data: Uint8Array): Uint8Array {
81
+ return this.blake3Instance.load(state).update(data).save();
82
+ }
83
+
84
+ blake3DigestForState(state: Uint8Array): Uint8Array {
85
+ return this.blake3Instance.load(state).digest("binary");
86
+ }
87
+
88
+ newEd25519SigningKey(): Uint8Array {
89
+ return new Ed25519SigningKey().to_bytes().copyAndDispose();
90
+ }
91
+
92
+ getSignerID(secret: SignerSecret): SignerID {
93
+ return `signer_z${base58.encode(
94
+ Ed25519SigningKey.from_bytes(
95
+ new Memory(
96
+ base58.decode(secret.substring("signerSecret_z".length)),
97
+ ),
98
+ )
99
+ .public()
100
+ .to_bytes()
101
+ .copyAndDispose(),
102
+ )}`;
103
+ }
104
+
105
+ sign(secret: SignerSecret, message: JsonValue): Signature {
106
+ const signature = Ed25519SigningKey.from_bytes(
107
+ new Memory(
108
+ base58.decode(secret.substring("signerSecret_z".length)),
109
+ ),
110
+ )
111
+ .sign(new Memory(textEncoder.encode(stableStringify(message))))
112
+ .to_bytes()
113
+ .copyAndDispose();
114
+ return `signature_z${base58.encode(signature)}`;
115
+ }
116
+
117
+ verify(signature: Signature, message: JsonValue, id: SignerID): boolean {
118
+ return new Ed25519VerifyingKey(
119
+ new Memory(base58.decode(id.substring("signer_z".length))),
120
+ ).verify(
121
+ new Memory(textEncoder.encode(stableStringify(message))),
122
+ new Ed25519Signature(
123
+ new Memory(
124
+ base58.decode(signature.substring("signature_z".length)),
125
+ ),
126
+ ),
127
+ );
128
+ }
129
+
130
+ newX25519StaticSecret(): Uint8Array {
131
+ return new X25519StaticSecret().to_bytes().copyAndDispose();
132
+ }
133
+
134
+ getSealerID(secret: SealerSecret): SealerID {
135
+ return `sealer_z${base58.encode(
136
+ X25519StaticSecret.from_bytes(
137
+ new Memory(
138
+ base58.decode(secret.substring("sealerSecret_z".length)),
139
+ ),
140
+ )
141
+ .to_public()
142
+ .to_bytes()
143
+ .copyAndDispose(),
144
+ )}`;
145
+ }
146
+
147
+ encrypt<T extends JsonValue, N extends JsonValue>(
148
+ value: T,
149
+ keySecret: KeySecret,
150
+ nOnceMaterial: N,
151
+ ): Encrypted<T, N> {
152
+ const keySecretBytes = base58.decode(
153
+ keySecret.substring("keySecret_z".length),
154
+ );
155
+ const nOnce = this.blake3HashOnce(
156
+ textEncoder.encode(stableStringify(nOnceMaterial)),
157
+ ).slice(0, 24);
158
+
159
+ const plaintext = textEncoder.encode(stableStringify(value));
160
+ const ciphertext = xsalsa20(keySecretBytes, nOnce, plaintext);
161
+ return `encrypted_U${bytesToBase64url(ciphertext)}` as Encrypted<T, N>;
162
+ }
163
+
164
+ decryptRaw<T extends JsonValue, N extends JsonValue>(
165
+ encrypted: Encrypted<T, N>,
166
+ keySecret: KeySecret,
167
+ nOnceMaterial: N,
168
+ ): Stringified<T> {
169
+ const keySecretBytes = base58.decode(
170
+ keySecret.substring("keySecret_z".length),
171
+ );
172
+ const nOnce = this.blake3HashOnce(
173
+ textEncoder.encode(stableStringify(nOnceMaterial)),
174
+ ).slice(0, 24);
175
+
176
+ const ciphertext = base64URLtoBytes(
177
+ encrypted.substring("encrypted_U".length),
178
+ );
179
+ const plaintext = xsalsa20(keySecretBytes, nOnce, ciphertext);
180
+
181
+ return textDecoder.decode(plaintext) as Stringified<T>;
182
+ }
183
+
184
+ seal<T extends JsonValue>({
185
+ message,
186
+ from,
187
+ to,
188
+ nOnceMaterial,
189
+ }: {
190
+ message: T;
191
+ from: SealerSecret;
192
+ to: SealerID;
193
+ nOnceMaterial: { in: RawCoID; tx: TransactionID };
194
+ }): Sealed<T> {
195
+ const nOnce = this.blake3HashOnce(
196
+ textEncoder.encode(stableStringify(nOnceMaterial)),
197
+ ).slice(0, 24);
198
+
199
+ const sealerPub = base58.decode(to.substring("sealer_z".length));
200
+
201
+ const senderPriv = base58.decode(
202
+ from.substring("sealerSecret_z".length),
203
+ );
204
+
205
+ const plaintext = textEncoder.encode(stableStringify(message));
206
+
207
+ const sharedSecret = X25519StaticSecret.from_bytes(
208
+ new Memory(senderPriv),
209
+ )
210
+ .diffie_hellman(X25519PublicKey.from_bytes(new Memory(sealerPub)))
211
+ .to_bytes()
212
+ .copyAndDispose();
213
+
214
+ const sealedBytes = xsalsa20_poly1305(sharedSecret, nOnce).encrypt(
215
+ plaintext,
216
+ );
217
+
218
+ return `sealed_U${bytesToBase64url(sealedBytes)}` as Sealed<T>;
219
+ }
220
+
221
+ unseal<T extends JsonValue>(
222
+ sealed: Sealed<T>,
223
+ sealer: SealerSecret,
224
+ from: SealerID,
225
+ nOnceMaterial: { in: RawCoID; tx: TransactionID },
226
+ ): T | undefined {
227
+ const nOnce = this.blake3HashOnce(
228
+ textEncoder.encode(stableStringify(nOnceMaterial)),
229
+ ).slice(0, 24);
230
+
231
+ const sealerPriv = base58.decode(
232
+ sealer.substring("sealerSecret_z".length),
233
+ );
234
+
235
+ const senderPub = base58.decode(from.substring("sealer_z".length));
236
+
237
+ const sealedBytes = base64URLtoBytes(
238
+ sealed.substring("sealed_U".length),
239
+ );
240
+
241
+ const sharedSecret = X25519StaticSecret.from_bytes(
242
+ new Memory(sealerPriv),
243
+ )
244
+ .diffie_hellman(X25519PublicKey.from_bytes(new Memory(senderPub)))
245
+ .to_bytes()
246
+ .copyAndDispose();
247
+
248
+ const plaintext = xsalsa20_poly1305(sharedSecret, nOnce).decrypt(
249
+ sealedBytes,
250
+ );
251
+
252
+ try {
253
+ return JSON.parse(textDecoder.decode(plaintext));
254
+ } catch (e) {
255
+ console.error("Failed to decrypt/parse sealed message", e);
256
+ return undefined;
257
+ }
258
+ }
259
+ }