cojson 0.1.11 → 0.1.12
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/dist/base64url.js +15 -2
- package/dist/base64url.js.map +1 -1
- package/dist/coValueCore.d.ts +5 -0
- package/dist/coValueCore.js +96 -4
- package/dist/coValueCore.js.map +1 -1
- package/dist/coValues/coStream.d.ts +1 -1
- package/dist/coValues/coStream.js +16 -10
- package/dist/coValues/coStream.js.map +1 -1
- package/dist/crypto.d.ts +3 -3
- package/dist/crypto.js +42 -14
- package/dist/crypto.js.map +1 -1
- package/dist/fastJsonStableStringify.d.ts +1 -0
- package/dist/fastJsonStableStringify.js +53 -0
- package/dist/fastJsonStableStringify.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/sync.js +8 -1
- package/dist/sync.js.map +1 -1
- package/package.json +3 -4
- package/src/account.test.ts +5 -0
- package/src/base64url.ts +16 -6
- package/src/coValue.test.ts +28 -0
- package/src/coValueCore.test.ts +5 -0
- package/src/coValueCore.ts +140 -4
- package/src/coValues/coStream.ts +29 -15
- package/src/crypto.test.ts +5 -0
- package/src/crypto.ts +47 -16
- package/src/fastJsonStableStringify.ts +54 -0
- package/src/group.test.ts +5 -1
- package/src/index.ts +2 -0
- package/src/permissions.test.ts +5 -1
- package/src/sync.test.ts +6 -6
- package/src/sync.ts +16 -1
package/src/crypto.ts
CHANGED
|
@@ -2,12 +2,39 @@ import { ed25519, x25519 } from "@noble/curves/ed25519";
|
|
|
2
2
|
import { xsalsa20_poly1305, xsalsa20 } from "@noble/ciphers/salsa";
|
|
3
3
|
import { JsonValue } from "./jsonValue.js";
|
|
4
4
|
import { base58 } from "@scure/base";
|
|
5
|
-
import stableStringify from "fast-json-stable-stringify";
|
|
6
|
-
import { blake3 } from "@noble/hashes/blake3";
|
|
7
5
|
import { randomBytes } from "@noble/ciphers/webcrypto/utils";
|
|
8
6
|
import { AgentID, RawCoID, TransactionID } from "./ids.js";
|
|
9
7
|
import { base64URLtoBytes, bytesToBase64url } from "./base64url.js";
|
|
10
8
|
|
|
9
|
+
import { createBLAKE3 } from 'hash-wasm';
|
|
10
|
+
import { stableStringify } from "./fastJsonStableStringify.js";
|
|
11
|
+
|
|
12
|
+
let blake3Instance: Awaited<ReturnType<typeof createBLAKE3>>;
|
|
13
|
+
let blake3HashOnce: (data: Uint8Array) => Uint8Array;
|
|
14
|
+
let blake3HashOnceWithContext: (data: Uint8Array, {context}: {context: Uint8Array}) => Uint8Array;
|
|
15
|
+
let blake3incrementalUpdateSLOW_WITH_DEVTOOLS: (state: Uint8Array, data: Uint8Array) => Uint8Array;
|
|
16
|
+
let blake3digestForState: (state: Uint8Array) => Uint8Array;
|
|
17
|
+
|
|
18
|
+
export const cryptoReady = new Promise<void>((resolve) => {
|
|
19
|
+
createBLAKE3().then(bl3 => {
|
|
20
|
+
blake3Instance = bl3;
|
|
21
|
+
blake3HashOnce = (data) => {
|
|
22
|
+
return bl3.init().update(data).digest('binary');
|
|
23
|
+
}
|
|
24
|
+
blake3HashOnceWithContext = (data, {context}) => {
|
|
25
|
+
return bl3.init().update(context).update(data).digest('binary');
|
|
26
|
+
}
|
|
27
|
+
blake3incrementalUpdateSLOW_WITH_DEVTOOLS = (state, data) => {
|
|
28
|
+
bl3.load(state).update(data);
|
|
29
|
+
return bl3.save();
|
|
30
|
+
}
|
|
31
|
+
blake3digestForState = (state) => {
|
|
32
|
+
return bl3.load(state).digest('binary');
|
|
33
|
+
}
|
|
34
|
+
resolve();
|
|
35
|
+
})
|
|
36
|
+
});
|
|
37
|
+
|
|
11
38
|
export type SignerSecret = `signerSecret_z${string}`;
|
|
12
39
|
export type SignerID = `signer_z${string}`;
|
|
13
40
|
export type Signature = `signature_z${string}`;
|
|
@@ -128,7 +155,7 @@ export function seal<T extends JsonValue>(
|
|
|
128
155
|
to: SealerID,
|
|
129
156
|
nOnceMaterial: { in: RawCoID; tx: TransactionID }
|
|
130
157
|
): Sealed<T> {
|
|
131
|
-
const nOnce =
|
|
158
|
+
const nOnce = blake3HashOnce(
|
|
132
159
|
textEncoder.encode(stableStringify(nOnceMaterial))
|
|
133
160
|
).slice(0, 24);
|
|
134
161
|
|
|
@@ -153,7 +180,7 @@ export function unseal<T extends JsonValue>(
|
|
|
153
180
|
from: SealerID,
|
|
154
181
|
nOnceMaterial: { in: RawCoID; tx: TransactionID }
|
|
155
182
|
): T | undefined {
|
|
156
|
-
const nOnce =
|
|
183
|
+
const nOnce = blake3HashOnce(
|
|
157
184
|
textEncoder.encode(stableStringify(nOnceMaterial))
|
|
158
185
|
).slice(0, 24);
|
|
159
186
|
|
|
@@ -181,28 +208,32 @@ export type Hash = `hash_z${string}`;
|
|
|
181
208
|
|
|
182
209
|
export function secureHash(value: JsonValue): Hash {
|
|
183
210
|
return `hash_z${base58.encode(
|
|
184
|
-
|
|
211
|
+
blake3HashOnce(textEncoder.encode(stableStringify(value)))
|
|
185
212
|
)}`;
|
|
186
213
|
}
|
|
187
214
|
|
|
188
215
|
export class StreamingHash {
|
|
189
|
-
state:
|
|
216
|
+
state: Uint8Array;
|
|
190
217
|
|
|
191
|
-
constructor(fromClone?:
|
|
192
|
-
this.state = fromClone ||
|
|
218
|
+
constructor(fromClone?: Uint8Array) {
|
|
219
|
+
this.state = fromClone || blake3Instance.init().save();
|
|
193
220
|
}
|
|
194
221
|
|
|
195
222
|
update(value: JsonValue) {
|
|
196
|
-
|
|
223
|
+
const encoded = textEncoder.encode(stableStringify(value))
|
|
224
|
+
// const before = performance.now();
|
|
225
|
+
this.state = blake3incrementalUpdateSLOW_WITH_DEVTOOLS(this.state, encoded);
|
|
226
|
+
// const after = performance.now();
|
|
227
|
+
// console.log(`Hashing throughput in MB/s`, 1000 * (encoded.length / (after - before)) / (1024 * 1024));
|
|
197
228
|
}
|
|
198
229
|
|
|
199
230
|
digest(): Hash {
|
|
200
|
-
const hash = this.state
|
|
231
|
+
const hash = blake3digestForState(this.state);
|
|
201
232
|
return `hash_z${base58.encode(hash)}`;
|
|
202
233
|
}
|
|
203
234
|
|
|
204
235
|
clone(): StreamingHash {
|
|
205
|
-
return new StreamingHash(this.state
|
|
236
|
+
return new StreamingHash(new Uint8Array(this.state));
|
|
206
237
|
}
|
|
207
238
|
}
|
|
208
239
|
|
|
@@ -211,7 +242,7 @@ export const shortHashLength = 19;
|
|
|
211
242
|
|
|
212
243
|
export function shortHash(value: JsonValue): ShortHash {
|
|
213
244
|
return `shortHash_z${base58.encode(
|
|
214
|
-
|
|
245
|
+
blake3HashOnce(textEncoder.encode(stableStringify(value))).slice(
|
|
215
246
|
0,
|
|
216
247
|
shortHashLength
|
|
217
248
|
)
|
|
@@ -241,7 +272,7 @@ function encrypt<T extends JsonValue, N extends JsonValue>(
|
|
|
241
272
|
const keySecretBytes = base58.decode(
|
|
242
273
|
keySecret.substring("keySecret_z".length)
|
|
243
274
|
);
|
|
244
|
-
const nOnce =
|
|
275
|
+
const nOnce = blake3HashOnce(
|
|
245
276
|
textEncoder.encode(stableStringify(nOnceMaterial))
|
|
246
277
|
).slice(0, 24);
|
|
247
278
|
|
|
@@ -293,7 +324,7 @@ function decrypt<T extends JsonValue, N extends JsonValue>(
|
|
|
293
324
|
const keySecretBytes = base58.decode(
|
|
294
325
|
keySecret.substring("keySecret_z".length)
|
|
295
326
|
);
|
|
296
|
-
const nOnce =
|
|
327
|
+
const nOnce = blake3HashOnce(
|
|
297
328
|
textEncoder.encode(stableStringify(nOnceMaterial))
|
|
298
329
|
).slice(0, 24);
|
|
299
330
|
|
|
@@ -365,11 +396,11 @@ export function agentSecretFromSecretSeed(secretSeed: Uint8Array): AgentSecret {
|
|
|
365
396
|
}
|
|
366
397
|
|
|
367
398
|
return `sealerSecret_z${base58.encode(
|
|
368
|
-
|
|
399
|
+
blake3HashOnceWithContext(secretSeed, {
|
|
369
400
|
context: textEncoder.encode("seal"),
|
|
370
401
|
})
|
|
371
402
|
)}/signerSecret_z${base58.encode(
|
|
372
|
-
|
|
403
|
+
blake3HashOnceWithContext(secretSeed, {
|
|
373
404
|
context: textEncoder.encode("sign"),
|
|
374
405
|
})
|
|
375
406
|
)}`;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// adapted from fast-json-stable-stringify (https://github.com/epoberezkin/fast-json-stable-stringify)
|
|
2
|
+
|
|
3
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4
|
+
export function stableStringify(data: any): string | undefined {
|
|
5
|
+
const cycles = false;
|
|
6
|
+
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
8
|
+
const seen: any[] = [];
|
|
9
|
+
let node = data;
|
|
10
|
+
|
|
11
|
+
if (node && node.toJSON && typeof node.toJSON === "function") {
|
|
12
|
+
node = node.toJSON();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (node === undefined) return;
|
|
16
|
+
if (typeof node == "number") return isFinite(node) ? "" + node : "null";
|
|
17
|
+
if (typeof node !== "object") {
|
|
18
|
+
if (typeof node === "string" && (node.startsWith("encrypted_U") || node.startsWith("binary_U"))) {
|
|
19
|
+
return `"${node}"`;
|
|
20
|
+
}
|
|
21
|
+
return JSON.stringify(node);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let i, out;
|
|
25
|
+
if (Array.isArray(node)) {
|
|
26
|
+
out = "[";
|
|
27
|
+
for (i = 0; i < node.length; i++) {
|
|
28
|
+
if (i) out += ",";
|
|
29
|
+
out += stableStringify(node[i]) || "null";
|
|
30
|
+
}
|
|
31
|
+
return out + "]";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (node === null) return "null";
|
|
35
|
+
|
|
36
|
+
if (seen.indexOf(node) !== -1) {
|
|
37
|
+
if (cycles) return JSON.stringify("__cycle__");
|
|
38
|
+
throw new TypeError("Converting circular structure to JSON");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const seenIndex = seen.push(node) - 1;
|
|
42
|
+
const keys = Object.keys(node).sort();
|
|
43
|
+
out = "";
|
|
44
|
+
for (i = 0; i < keys.length; i++) {
|
|
45
|
+
const key = keys[i]!;
|
|
46
|
+
const value = stableStringify(node[key]);
|
|
47
|
+
|
|
48
|
+
if (!value) continue;
|
|
49
|
+
if (out) out += ",";
|
|
50
|
+
out += JSON.stringify(key) + ":" + value;
|
|
51
|
+
}
|
|
52
|
+
seen.splice(seenIndex, 1);
|
|
53
|
+
return "{" + out + "}";
|
|
54
|
+
}
|
package/src/group.test.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import { LocalNode, CoMap, CoList, CoStream, BinaryCoStream } from "./index";
|
|
1
|
+
import { LocalNode, CoMap, CoList, CoStream, BinaryCoStream, cojsonReady } from "./index";
|
|
2
2
|
import { randomAnonymousAccountAndSessionID } from "./testUtils";
|
|
3
3
|
|
|
4
|
+
beforeEach(async () => {
|
|
5
|
+
await cojsonReady;
|
|
6
|
+
});
|
|
7
|
+
|
|
4
8
|
test("Can create a CoMap in a group", () => {
|
|
5
9
|
const node = new LocalNode(...randomAnonymousAccountAndSessionID());
|
|
6
10
|
|
package/src/index.ts
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
agentSecretFromSecretSeed,
|
|
19
19
|
secretSeedLength,
|
|
20
20
|
shortHashLength,
|
|
21
|
+
cryptoReady
|
|
21
22
|
} from "./crypto.js";
|
|
22
23
|
import { connectedPeers } from "./streamUtils.js";
|
|
23
24
|
import { AnonymousControlledAccount, ControlledAccount } from "./account.js";
|
|
@@ -69,6 +70,7 @@ export {
|
|
|
69
70
|
CoValueCore,
|
|
70
71
|
AnonymousControlledAccount,
|
|
71
72
|
ControlledAccount,
|
|
73
|
+
cryptoReady as cojsonReady,
|
|
72
74
|
};
|
|
73
75
|
|
|
74
76
|
export type {
|
package/src/permissions.test.ts
CHANGED
|
@@ -17,7 +17,11 @@ import {
|
|
|
17
17
|
groupWithTwoAdmins,
|
|
18
18
|
groupWithTwoAdminsHighLevel,
|
|
19
19
|
} from "./testUtils.js";
|
|
20
|
-
import { AnonymousControlledAccount } from "./index.js";
|
|
20
|
+
import { AnonymousControlledAccount, cojsonReady } from "./index.js";
|
|
21
|
+
|
|
22
|
+
beforeEach(async () => {
|
|
23
|
+
await cojsonReady;
|
|
24
|
+
});
|
|
21
25
|
|
|
22
26
|
test("Initial admin can add another admin to a group", () => {
|
|
23
27
|
groupWithTwoAdmins();
|
package/src/sync.test.ts
CHANGED
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
import { newRandomSessionID } from "./coValueCore.js";
|
|
2
2
|
import { LocalNode } from "./node.js";
|
|
3
|
-
import {
|
|
3
|
+
import { SyncMessage } from "./sync.js";
|
|
4
4
|
import { expectMap } from "./coValue.js";
|
|
5
5
|
import { MapOpPayload } from "./coValues/coMap.js";
|
|
6
6
|
import { Group } from "./group.js";
|
|
7
|
-
import {
|
|
8
|
-
ReadableStream,
|
|
9
|
-
WritableStream,
|
|
10
|
-
TransformStream,
|
|
11
|
-
} from "isomorphic-streams";
|
|
12
7
|
import {
|
|
13
8
|
randomAnonymousAccountAndSessionID,
|
|
14
9
|
shouldNotResolve,
|
|
@@ -18,6 +13,11 @@ import {
|
|
|
18
13
|
newStreamPair
|
|
19
14
|
} from "./streamUtils.js";
|
|
20
15
|
import { AccountID } from "./account.js";
|
|
16
|
+
import { cojsonReady } from "./index.js";
|
|
17
|
+
|
|
18
|
+
beforeEach(async () => {
|
|
19
|
+
await cojsonReady;
|
|
20
|
+
});
|
|
21
21
|
|
|
22
22
|
test("Node replies with initial tx and header to empty subscribe", async () => {
|
|
23
23
|
const [admin, session] = randomAnonymousAccountAndSessionID();
|
package/src/sync.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
WritableStreamDefaultWriter,
|
|
10
10
|
} from "isomorphic-streams";
|
|
11
11
|
import { RawCoID, SessionID } from "./ids.js";
|
|
12
|
+
import { stableStringify } from "./fastJsonStableStringify.js";
|
|
12
13
|
|
|
13
14
|
export type CoValueKnownState = {
|
|
14
15
|
id: RawCoID;
|
|
@@ -445,12 +446,26 @@ export class SyncManager {
|
|
|
445
446
|
const newTransactions =
|
|
446
447
|
newContentForSession.newTransactions.slice(alreadyKnownOffset);
|
|
447
448
|
|
|
448
|
-
const
|
|
449
|
+
const before = performance.now();
|
|
450
|
+
const success = await coValue.tryAddTransactionsAsync(
|
|
449
451
|
sessionID,
|
|
450
452
|
newTransactions,
|
|
451
453
|
undefined,
|
|
452
454
|
newContentForSession.lastSignature
|
|
453
455
|
);
|
|
456
|
+
const after = performance.now();
|
|
457
|
+
if (after - before > 10) {
|
|
458
|
+
const totalTxLength = newTransactions.map(t => stableStringify(t)!.length).reduce((a, b) => a + b, 0);
|
|
459
|
+
console.log(
|
|
460
|
+
"Adding incoming transactions took",
|
|
461
|
+
after - before,
|
|
462
|
+
"ms",
|
|
463
|
+
totalTxLength,
|
|
464
|
+
"bytes = ",
|
|
465
|
+
"bandwidth: MB/s",
|
|
466
|
+
(1000 * totalTxLength / (after - before)) / (1024 * 1024)
|
|
467
|
+
);
|
|
468
|
+
}
|
|
454
469
|
|
|
455
470
|
if (!success) {
|
|
456
471
|
console.error("Failed to add transactions", newTransactions);
|