cojson 0.7.0-alpha.5 → 0.7.0

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 (113) hide show
  1. package/.eslintrc.cjs +3 -2
  2. package/.prettierrc.js +9 -0
  3. package/.turbo/turbo-build.log +3 -30
  4. package/.turbo/turbo-lint.log +4 -0
  5. package/.turbo/turbo-test.log +1106 -0
  6. package/CHANGELOG.md +104 -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 +60 -37
  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/coList.js +1 -1
  15. package/dist/coValues/coList.js.map +1 -1
  16. package/dist/coValues/coMap.js +17 -8
  17. package/dist/coValues/coMap.js.map +1 -1
  18. package/dist/coValues/group.js +13 -14
  19. package/dist/coValues/group.js.map +1 -1
  20. package/dist/coreToCoValue.js.map +1 -1
  21. package/dist/crypto/PureJSCrypto.js +89 -0
  22. package/dist/crypto/PureJSCrypto.js.map +1 -0
  23. package/dist/crypto/WasmCrypto.js +127 -0
  24. package/dist/crypto/WasmCrypto.js.map +1 -0
  25. package/dist/crypto/crypto.js +151 -0
  26. package/dist/crypto/crypto.js.map +1 -0
  27. package/dist/ids.js +4 -2
  28. package/dist/ids.js.map +1 -1
  29. package/dist/index.js +6 -8
  30. package/dist/index.js.map +1 -1
  31. package/dist/jsonStringify.js.map +1 -1
  32. package/dist/localNode.js +41 -38
  33. package/dist/localNode.js.map +1 -1
  34. package/dist/permissions.js +6 -6
  35. package/dist/permissions.js.map +1 -1
  36. package/dist/storage/FileSystem.js +61 -0
  37. package/dist/storage/FileSystem.js.map +1 -0
  38. package/dist/storage/chunksAndKnownStates.js +97 -0
  39. package/dist/storage/chunksAndKnownStates.js.map +1 -0
  40. package/dist/storage/index.js +265 -0
  41. package/dist/storage/index.js.map +1 -0
  42. package/dist/sync.js +29 -25
  43. package/dist/sync.js.map +1 -1
  44. package/dist/tests/account.test.js +58 -0
  45. package/dist/tests/account.test.js.map +1 -0
  46. package/dist/tests/coList.test.js +76 -0
  47. package/dist/tests/coList.test.js.map +1 -0
  48. package/dist/tests/coMap.test.js +136 -0
  49. package/dist/tests/coMap.test.js.map +1 -0
  50. package/dist/tests/coStream.test.js +172 -0
  51. package/dist/tests/coStream.test.js.map +1 -0
  52. package/dist/tests/coValueCore.test.js +114 -0
  53. package/dist/tests/coValueCore.test.js.map +1 -0
  54. package/dist/tests/crypto.test.js +118 -0
  55. package/dist/tests/crypto.test.js.map +1 -0
  56. package/dist/tests/cryptoImpl.test.js +113 -0
  57. package/dist/tests/cryptoImpl.test.js.map +1 -0
  58. package/dist/tests/group.test.js +34 -0
  59. package/dist/tests/group.test.js.map +1 -0
  60. package/dist/tests/permissions.test.js +1060 -0
  61. package/dist/tests/permissions.test.js.map +1 -0
  62. package/dist/tests/sync.test.js +816 -0
  63. package/dist/tests/sync.test.js.map +1 -0
  64. package/dist/tests/testUtils.js +12 -11
  65. package/dist/tests/testUtils.js.map +1 -1
  66. package/dist/typeUtils/accountOrAgentIDfromSessionID.js.map +1 -1
  67. package/dist/typeUtils/isAccountID.js.map +1 -1
  68. package/dist/typeUtils/isCoValue.js.map +1 -1
  69. package/package.json +14 -27
  70. package/src/base64url.test.ts +6 -5
  71. package/src/coValue.ts +1 -1
  72. package/src/coValueCore.ts +179 -126
  73. package/src/coValues/account.ts +30 -32
  74. package/src/coValues/coList.ts +11 -11
  75. package/src/coValues/coMap.ts +27 -17
  76. package/src/coValues/coStream.ts +17 -17
  77. package/src/coValues/group.ts +93 -109
  78. package/src/coreToCoValue.ts +5 -2
  79. package/src/crypto/PureJSCrypto.ts +200 -0
  80. package/src/crypto/WasmCrypto.ts +259 -0
  81. package/src/crypto/crypto.ts +336 -0
  82. package/src/ids.ts +8 -7
  83. package/src/index.ts +24 -24
  84. package/src/jsonStringify.ts +6 -4
  85. package/src/jsonValue.ts +2 -2
  86. package/src/localNode.ts +103 -109
  87. package/src/media.ts +3 -3
  88. package/src/permissions.ts +19 -21
  89. package/src/storage/FileSystem.ts +152 -0
  90. package/src/storage/chunksAndKnownStates.ts +139 -0
  91. package/src/storage/index.ts +479 -0
  92. package/src/streamUtils.ts +12 -12
  93. package/src/sync.ts +79 -63
  94. package/src/tests/account.test.ts +15 -15
  95. package/src/tests/coList.test.ts +94 -0
  96. package/src/tests/coMap.test.ts +162 -0
  97. package/src/tests/coStream.test.ts +246 -0
  98. package/src/tests/coValueCore.test.ts +36 -37
  99. package/src/tests/crypto.test.ts +66 -72
  100. package/src/tests/cryptoImpl.test.ts +183 -0
  101. package/src/tests/group.test.ts +16 -17
  102. package/src/tests/permissions.test.ts +269 -283
  103. package/src/tests/sync.test.ts +122 -123
  104. package/src/tests/testUtils.ts +24 -21
  105. package/src/typeUtils/accountOrAgentIDfromSessionID.ts +1 -2
  106. package/src/typeUtils/expectGroup.ts +1 -1
  107. package/src/typeUtils/isAccountID.ts +0 -1
  108. package/src/typeUtils/isCoValue.ts +1 -2
  109. package/tsconfig.json +0 -1
  110. package/dist/crypto.js +0 -254
  111. package/dist/crypto.js.map +0 -1
  112. package/src/crypto.ts +0 -484
  113. package/src/tests/coValue.test.ts +0 -497
@@ -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
+ }
@@ -0,0 +1,336 @@
1
+ import { JsonValue } from "../jsonValue.js";
2
+ import { base58 } from "@scure/base";
3
+ import { AgentID, RawCoID, TransactionID } from "../ids.js";
4
+ import { Stringified, parseJSON, stableStringify } from "../jsonStringify.js";
5
+
6
+ export type SignerSecret = `signerSecret_z${string}`;
7
+ export type SignerID = `signer_z${string}`;
8
+ export type Signature = `signature_z${string}`;
9
+
10
+ export type SealerSecret = `sealerSecret_z${string}`;
11
+ export type SealerID = `sealer_z${string}`;
12
+ export type Sealed<T> = `sealed_U${string}` & { __type: T };
13
+
14
+ export type AgentSecret = `${SealerSecret}/${SignerSecret}`;
15
+
16
+ export const textEncoder = new TextEncoder();
17
+ export const textDecoder = new TextDecoder();
18
+
19
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
20
+ export abstract class CryptoProvider<Blake3State = any> {
21
+ abstract randomBytes(length: number): Uint8Array;
22
+
23
+ abstract newEd25519SigningKey(): Uint8Array;
24
+
25
+ newRandomSigner(): SignerSecret {
26
+ return `signerSecret_z${base58.encode(this.newEd25519SigningKey())}`;
27
+ }
28
+
29
+ signerSecretToBytes(secret: SignerSecret): Uint8Array {
30
+ return base58.decode(secret.substring("signerSecret_z".length));
31
+ }
32
+
33
+ signerSecretFromBytes(bytes: Uint8Array): SignerSecret {
34
+ return `signerSecret_z${base58.encode(bytes)}`;
35
+ }
36
+
37
+ abstract getSignerID(secret: SignerSecret): SignerID;
38
+
39
+ abstract sign(secret: SignerSecret, message: JsonValue): Signature;
40
+
41
+ abstract verify(
42
+ signature: Signature,
43
+ message: JsonValue,
44
+ id: SignerID,
45
+ ): boolean;
46
+
47
+ abstract newX25519StaticSecret(): Uint8Array;
48
+
49
+ newRandomSealer(): SealerSecret {
50
+ return `sealerSecret_z${base58.encode(this.newX25519StaticSecret())}`;
51
+ }
52
+
53
+ sealerSecretToBytes(secret: SealerSecret): Uint8Array {
54
+ return base58.decode(secret.substring("sealerSecret_z".length));
55
+ }
56
+
57
+ sealerSecretFromBytes(bytes: Uint8Array): SealerSecret {
58
+ return `sealerSecret_z${base58.encode(bytes)}`;
59
+ }
60
+
61
+ abstract getSealerID(secret: SealerSecret): SealerID;
62
+
63
+ newRandomAgentSecret(): AgentSecret {
64
+ return `${this.newRandomSealer()}/${this.newRandomSigner()}`;
65
+ }
66
+
67
+ agentSecretToBytes(secret: AgentSecret): Uint8Array {
68
+ const [sealerSecret, signerSecret] = secret.split("/");
69
+ return new Uint8Array([
70
+ ...this.sealerSecretToBytes(sealerSecret as SealerSecret),
71
+ ...this.signerSecretToBytes(signerSecret as SignerSecret),
72
+ ]);
73
+ }
74
+
75
+ agentSecretFromBytes(bytes: Uint8Array): AgentSecret {
76
+ const sealerSecret = this.sealerSecretFromBytes(bytes.slice(0, 32));
77
+ const signerSecret = this.signerSecretFromBytes(bytes.slice(32));
78
+ return `${sealerSecret}/${signerSecret}`;
79
+ }
80
+
81
+ getAgentID(secret: AgentSecret): AgentID {
82
+ const [sealerSecret, signerSecret] = secret.split("/");
83
+ return `${this.getSealerID(
84
+ sealerSecret as SealerSecret,
85
+ )}/${this.getSignerID(signerSecret as SignerSecret)}`;
86
+ }
87
+
88
+ getAgentSignerID(agentId: AgentID): SignerID {
89
+ return agentId.split("/")[1] as SignerID;
90
+ }
91
+
92
+ getAgentSignerSecret(agentSecret: AgentSecret): SignerSecret {
93
+ return agentSecret.split("/")[1] as SignerSecret;
94
+ }
95
+
96
+ getAgentSealerID(agentId: AgentID): SealerID {
97
+ return agentId.split("/")[0] as SealerID;
98
+ }
99
+
100
+ getAgentSealerSecret(agentSecret: AgentSecret): SealerSecret {
101
+ return agentSecret.split("/")[0] as SealerSecret;
102
+ }
103
+
104
+ abstract emptyBlake3State(): Blake3State;
105
+ abstract blake3HashOnce(data: Uint8Array): Uint8Array;
106
+ abstract blake3HashOnceWithContext(
107
+ data: Uint8Array,
108
+ { context }: { context: Uint8Array },
109
+ ): Uint8Array;
110
+ abstract blake3IncrementalUpdate(
111
+ state: Blake3State,
112
+ data: Uint8Array,
113
+ ): Blake3State;
114
+ abstract blake3DigestForState(state: Blake3State): Uint8Array;
115
+
116
+ secureHash(value: JsonValue): Hash {
117
+ return `hash_z${base58.encode(
118
+ this.blake3HashOnce(textEncoder.encode(stableStringify(value))),
119
+ )}`;
120
+ }
121
+
122
+ shortHash(value: JsonValue): ShortHash {
123
+ return `shortHash_z${base58.encode(
124
+ this.blake3HashOnce(
125
+ textEncoder.encode(stableStringify(value)),
126
+ ).slice(0, shortHashLength),
127
+ )}`;
128
+ }
129
+
130
+ abstract encrypt<T extends JsonValue, N extends JsonValue>(
131
+ value: T,
132
+ keySecret: KeySecret,
133
+ nOnceMaterial: N,
134
+ ): Encrypted<T, N>;
135
+
136
+ encryptForTransaction<T extends JsonValue>(
137
+ value: T,
138
+ keySecret: KeySecret,
139
+ nOnceMaterial: { in: RawCoID; tx: TransactionID },
140
+ ): Encrypted<T, { in: RawCoID; tx: TransactionID }> {
141
+ return this.encrypt(value, keySecret, nOnceMaterial);
142
+ }
143
+
144
+ abstract decryptRaw<T extends JsonValue, N extends JsonValue>(
145
+ encrypted: Encrypted<T, N>,
146
+ keySecret: KeySecret,
147
+ nOnceMaterial: N,
148
+ ): Stringified<T>;
149
+
150
+ decrypt<T extends JsonValue, N extends JsonValue>(
151
+ encrypted: Encrypted<T, N>,
152
+ keySecret: KeySecret,
153
+ nOnceMaterial: N,
154
+ ): T | undefined {
155
+ try {
156
+ return parseJSON(
157
+ this.decryptRaw(encrypted, keySecret, nOnceMaterial),
158
+ );
159
+ } catch (e) {
160
+ console.error("Decryption error", e);
161
+ return undefined;
162
+ }
163
+ }
164
+
165
+ newRandomKeySecret(): { secret: KeySecret; id: KeyID } {
166
+ return {
167
+ secret: `keySecret_z${base58.encode(this.randomBytes(32))}`,
168
+ id: `key_z${base58.encode(this.randomBytes(12))}`,
169
+ };
170
+ }
171
+
172
+ decryptRawForTransaction<T extends JsonValue>(
173
+ encrypted: Encrypted<T, { in: RawCoID; tx: TransactionID }>,
174
+ keySecret: KeySecret,
175
+ nOnceMaterial: { in: RawCoID; tx: TransactionID },
176
+ ): Stringified<T> | undefined {
177
+ return this.decryptRaw(encrypted, keySecret, nOnceMaterial);
178
+ }
179
+
180
+ decryptForTransaction<T extends JsonValue>(
181
+ encrypted: Encrypted<T, { in: RawCoID; tx: TransactionID }>,
182
+ keySecret: KeySecret,
183
+ nOnceMaterial: { in: RawCoID; tx: TransactionID },
184
+ ): T | undefined {
185
+ return this.decrypt(encrypted, keySecret, nOnceMaterial);
186
+ }
187
+
188
+ encryptKeySecret(keys: {
189
+ toEncrypt: { id: KeyID; secret: KeySecret };
190
+ encrypting: { id: KeyID; secret: KeySecret };
191
+ }): {
192
+ encryptedID: KeyID;
193
+ encryptingID: KeyID;
194
+ encrypted: Encrypted<
195
+ KeySecret,
196
+ { encryptedID: KeyID; encryptingID: KeyID }
197
+ >;
198
+ } {
199
+ const nOnceMaterial = {
200
+ encryptedID: keys.toEncrypt.id,
201
+ encryptingID: keys.encrypting.id,
202
+ };
203
+
204
+ return {
205
+ encryptedID: keys.toEncrypt.id,
206
+ encryptingID: keys.encrypting.id,
207
+ encrypted: this.encrypt(
208
+ keys.toEncrypt.secret,
209
+ keys.encrypting.secret,
210
+ nOnceMaterial,
211
+ ),
212
+ };
213
+ }
214
+
215
+ decryptKeySecret(
216
+ encryptedInfo: {
217
+ encryptedID: KeyID;
218
+ encryptingID: KeyID;
219
+ encrypted: Encrypted<
220
+ KeySecret,
221
+ { encryptedID: KeyID; encryptingID: KeyID }
222
+ >;
223
+ },
224
+ sealingSecret: KeySecret,
225
+ ): KeySecret | undefined {
226
+ const nOnceMaterial = {
227
+ encryptedID: encryptedInfo.encryptedID,
228
+ encryptingID: encryptedInfo.encryptingID,
229
+ };
230
+
231
+ return this.decrypt(
232
+ encryptedInfo.encrypted,
233
+ sealingSecret,
234
+ nOnceMaterial,
235
+ );
236
+ }
237
+
238
+ abstract seal<T extends JsonValue>({
239
+ message,
240
+ from,
241
+ to,
242
+ nOnceMaterial,
243
+ }: {
244
+ message: T;
245
+ from: SealerSecret;
246
+ to: SealerID;
247
+ nOnceMaterial: { in: RawCoID; tx: TransactionID };
248
+ }): Sealed<T>;
249
+
250
+ abstract unseal<T extends JsonValue>(
251
+ sealed: Sealed<T>,
252
+ sealer: SealerSecret,
253
+ from: SealerID,
254
+ nOnceMaterial: { in: RawCoID; tx: TransactionID },
255
+ ): T | undefined;
256
+
257
+ uniquenessForHeader(): `z${string}` {
258
+ return `z${base58.encode(this.randomBytes(12))}`;
259
+ }
260
+
261
+ createdNowUnique(): {
262
+ createdAt: `2${string}`;
263
+ uniqueness: `z${string}`;
264
+ } {
265
+ const createdAt = new Date().toISOString() as `2${string}`;
266
+ return {
267
+ createdAt,
268
+ uniqueness: this.uniquenessForHeader(),
269
+ };
270
+ }
271
+
272
+ newRandomSecretSeed(): Uint8Array {
273
+ return this.randomBytes(secretSeedLength);
274
+ }
275
+
276
+ agentSecretFromSecretSeed(secretSeed: Uint8Array): AgentSecret {
277
+ if (secretSeed.length !== secretSeedLength) {
278
+ throw new Error(
279
+ `Secret seed needs to be ${secretSeedLength} bytes long`,
280
+ );
281
+ }
282
+
283
+ return `sealerSecret_z${base58.encode(
284
+ this.blake3HashOnceWithContext(secretSeed, {
285
+ context: textEncoder.encode("seal"),
286
+ }),
287
+ )}/signerSecret_z${base58.encode(
288
+ this.blake3HashOnceWithContext(secretSeed, {
289
+ context: textEncoder.encode("sign"),
290
+ }),
291
+ )}`;
292
+ }
293
+ }
294
+
295
+ export type Hash = `hash_z${string}`;
296
+
297
+ export class StreamingHash {
298
+ state: Uint8Array;
299
+ crypto: CryptoProvider;
300
+
301
+ constructor(crypto: CryptoProvider, fromClone?: Uint8Array) {
302
+ this.state = fromClone || crypto.emptyBlake3State();
303
+ this.crypto = crypto;
304
+ }
305
+
306
+ update(value: JsonValue): Uint8Array {
307
+ const encoded = textEncoder.encode(stableStringify(value));
308
+ // const before = performance.now();
309
+ this.state = this.crypto.blake3IncrementalUpdate(this.state, encoded);
310
+ // const after = performance.now();
311
+ // console.log(`Hashing throughput in MB/s`, 1000 * (encoded.length / (after - before)) / (1024 * 1024));
312
+ return encoded;
313
+ }
314
+
315
+ digest(): Hash {
316
+ const hash = this.crypto.blake3DigestForState(this.state);
317
+ return `hash_z${base58.encode(hash)}`;
318
+ }
319
+
320
+ clone(): StreamingHash {
321
+ return new StreamingHash(this.crypto, new Uint8Array(this.state));
322
+ }
323
+ }
324
+
325
+ export type ShortHash = `shortHash_z${string}`;
326
+ export const shortHashLength = 19;
327
+
328
+ export type Encrypted<
329
+ T extends JsonValue,
330
+ N extends JsonValue,
331
+ > = `encrypted_U${string}` & { __type: T; __nOnceMaterial: N };
332
+
333
+ export type KeySecret = `keySecret_z${string}`;
334
+ export type KeyID = `key_z${string}`;
335
+
336
+ export const secretSeedLength = 32;
package/src/ids.ts CHANGED
@@ -1,7 +1,6 @@
1
- import { AccountID } from './coValues/account.js';
1
+ import { AccountID } from "./coValues/account.js";
2
2
  import { base58 } from "@scure/base";
3
- import { shortHashLength } from './crypto.js';
4
-
3
+ import { shortHashLength } from "./crypto/crypto.js";
5
4
 
6
5
  export type RawCoID = `co_z${string}`;
7
6
 
@@ -10,9 +9,7 @@ export function isRawCoID(id: unknown): id is RawCoID {
10
9
  }
11
10
 
12
11
  export function rawCoIDtoBytes(id: RawCoID): Uint8Array {
13
- return base58.decode(
14
- id.substring("co_z".length)
15
- )
12
+ return base58.decode(id.substring("co_z".length));
16
13
  }
17
14
 
18
15
  export function rawCoIDfromBytes(bytes: Uint8Array): RawCoID {
@@ -24,7 +21,11 @@ export type TransactionID = { sessionID: SessionID; txIndex: number };
24
21
  export type AgentID = `sealer_z${string}/signer_z${string}`;
25
22
 
26
23
  export function isAgentID(id: string): id is AgentID {
27
- return typeof id === "string" && id.startsWith("sealer_") && id.includes("/signer_");
24
+ return (
25
+ typeof id === "string" &&
26
+ id.startsWith("sealer_") &&
27
+ id.includes("/signer_")
28
+ );
28
29
  }
29
30
 
30
31
  export type SessionID = `${AccountID | AgentID}_session_z${string}`;