cojson 0.7.0-alpha.7 → 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 +98 -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
@@ -3,26 +3,9 @@ import { RawCoMap } from "./coMap.js";
3
3
  import { RawCoList } from "./coList.js";
4
4
  import { JsonObject } from "../jsonValue.js";
5
5
  import { RawBinaryCoStream, RawCoStream } from "./coStream.js";
6
- import {
7
- Encrypted,
8
- KeyID,
9
- KeySecret,
10
- createdNowUnique,
11
- newRandomKeySecret,
12
- seal,
13
- encryptKeySecret,
14
- getAgentSealerID,
15
- Sealed,
16
- newRandomSecretSeed,
17
- agentSecretFromSecretSeed,
18
- getAgentID,
19
- } from "../crypto.js";
6
+ import { Encrypted, KeyID, KeySecret, Sealed } from "../crypto/crypto.js";
20
7
  import { AgentID, isAgentID } from "../ids.js";
21
- import {
22
- RawAccount,
23
- AccountID,
24
- ControlledAccountOrAgent,
25
- } from "./account.js";
8
+ import { RawAccount, AccountID, ControlledAccountOrAgent } from "./account.js";
26
9
  import { Role } from "../permissions.js";
27
10
  import { base58 } from "@scure/base";
28
11
 
@@ -65,7 +48,7 @@ export type GroupShape = {
65
48
  * ```
66
49
  * */
67
50
  export class RawGroup<
68
- Meta extends JsonObject | null = JsonObject | null
51
+ Meta extends JsonObject | null = JsonObject | null,
69
52
  > extends RawCoMap<GroupShape, Meta> {
70
53
  /**
71
54
  * Returns the current role of a given account.
@@ -98,7 +81,7 @@ export class RawGroup<
98
81
  */
99
82
  addMember(
100
83
  account: RawAccount | ControlledAccountOrAgent | Everyone,
101
- role: Role
84
+ role: Role,
102
85
  ) {
103
86
  this.addMemberInternal(account, role);
104
87
  }
@@ -106,58 +89,58 @@ export class RawGroup<
106
89
  /** @internal */
107
90
  addMemberInternal(
108
91
  account: RawAccount | ControlledAccountOrAgent | AgentID | Everyone,
109
- role: Role
92
+ role: Role,
110
93
  ) {
111
- const currentReadKey = this.core.getCurrentReadKey();
94
+ const currentReadKey = this.core.getCurrentReadKey();
112
95
 
113
- if (!currentReadKey.secret) {
114
- throw new Error("Can't add member without read key secret");
115
- }
96
+ if (!currentReadKey.secret) {
97
+ throw new Error("Can't add member without read key secret");
98
+ }
116
99
 
117
- if (account === EVERYONE) {
118
- if (!(role === "reader" || role === "writer")) {
119
- throw new Error(
120
- "Can't make everyone something other than reader or writer"
121
- );
122
- }
123
- this.set(account, role, "trusting");
124
-
125
- if (this.get(account) !== role) {
126
- throw new Error("Failed to set role");
127
- }
128
-
129
- this.set(
130
- `${currentReadKey.id}_for_${EVERYONE}`,
131
- currentReadKey.secret,
132
- "trusting"
133
- );
134
- } else {
135
- const memberKey =
136
- typeof account === "string" ? account : account.id;
137
- const agent =
138
- typeof account === "string"
139
- ? account
140
- : account.currentAgentID();
141
- this.set(memberKey, role, "trusting");
142
-
143
- if (this.get(memberKey) !== role) {
144
- throw new Error("Failed to set role");
145
- }
146
-
147
- this.set(
148
- `${currentReadKey.id}_for_${memberKey}`,
149
- seal({
150
- message: currentReadKey.secret,
151
- from: this.core.node.account.currentSealerSecret(),
152
- to: getAgentSealerID(agent),
153
- nOnceMaterial: {
154
- in: this.id,
155
- tx: this.core.nextTransactionID(),
156
- },
157
- }),
158
- "trusting"
100
+ if (account === EVERYONE) {
101
+ if (!(role === "reader" || role === "writer")) {
102
+ throw new Error(
103
+ "Can't make everyone something other than reader or writer",
159
104
  );
160
105
  }
106
+ this.set(account, role, "trusting");
107
+
108
+ if (this.get(account) !== role) {
109
+ throw new Error("Failed to set role");
110
+ }
111
+
112
+ this.set(
113
+ `${currentReadKey.id}_for_${EVERYONE}`,
114
+ currentReadKey.secret,
115
+ "trusting",
116
+ );
117
+ } else {
118
+ const memberKey =
119
+ typeof account === "string" ? account : account.id;
120
+ const agent =
121
+ typeof account === "string"
122
+ ? account
123
+ : account.currentAgentID();
124
+ this.set(memberKey, role, "trusting");
125
+
126
+ if (this.get(memberKey) !== role) {
127
+ throw new Error("Failed to set role");
128
+ }
129
+
130
+ this.set(
131
+ `${currentReadKey.id}_for_${memberKey}`,
132
+ this.core.crypto.seal({
133
+ message: currentReadKey.secret,
134
+ from: this.core.node.account.currentSealerSecret(),
135
+ to: this.core.crypto.getAgentSealerID(agent),
136
+ nOnceMaterial: {
137
+ in: this.id,
138
+ tx: this.core.nextTransactionID(),
139
+ },
140
+ }),
141
+ "trusting",
142
+ );
143
+ }
161
144
  }
162
145
 
163
146
  /** @internal */
@@ -177,7 +160,7 @@ export class RawGroup<
177
160
 
178
161
  if (!maybeCurrentReadKey.secret) {
179
162
  throw new Error(
180
- "Can't rotate read key secret we don't have access to"
163
+ "Can't rotate read key secret we don't have access to",
181
164
  );
182
165
  }
183
166
 
@@ -186,39 +169,39 @@ export class RawGroup<
186
169
  secret: maybeCurrentReadKey.secret,
187
170
  };
188
171
 
189
- const newReadKey = newRandomKeySecret();
172
+ const newReadKey = this.core.crypto.newRandomKeySecret();
190
173
 
191
- for (const readerID of currentlyPermittedReaders) {
192
- const reader = this.core.node.resolveAccountAgent(
193
- readerID,
194
- "Expected to know currently permitted reader"
195
- );
196
-
197
- this.set(
198
- `${newReadKey.id}_for_${readerID}`,
199
- seal({
200
- message: newReadKey.secret,
201
- from: this.core.node.account.currentSealerSecret(),
202
- to: getAgentSealerID(reader),
203
- nOnceMaterial: {
204
- in: this.id,
205
- tx: this.core.nextTransactionID(),
206
- },
207
- }),
208
- "trusting"
209
- );
210
- }
174
+ for (const readerID of currentlyPermittedReaders) {
175
+ const reader = this.core.node.resolveAccountAgent(
176
+ readerID,
177
+ "Expected to know currently permitted reader",
178
+ );
211
179
 
212
180
  this.set(
213
- `${currentReadKey.id}_for_${newReadKey.id}`,
214
- encryptKeySecret({
215
- encrypting: newReadKey,
216
- toEncrypt: currentReadKey,
217
- }).encrypted,
218
- "trusting"
181
+ `${newReadKey.id}_for_${readerID}`,
182
+ this.core.crypto.seal({
183
+ message: newReadKey.secret,
184
+ from: this.core.node.account.currentSealerSecret(),
185
+ to: this.core.crypto.getAgentSealerID(reader),
186
+ nOnceMaterial: {
187
+ in: this.id,
188
+ tx: this.core.nextTransactionID(),
189
+ },
190
+ }),
191
+ "trusting",
219
192
  );
193
+ }
220
194
 
221
- this.set("readKey", newReadKey.id, "trusting");
195
+ this.set(
196
+ `${currentReadKey.id}_for_${newReadKey.id}`,
197
+ this.core.crypto.encryptKeySecret({
198
+ encrypting: newReadKey,
199
+ toEncrypt: currentReadKey,
200
+ }).encrypted,
201
+ "trusting",
202
+ );
203
+
204
+ this.set("readKey", newReadKey.id, "trusting");
222
205
  }
223
206
 
224
207
  /**
@@ -234,10 +217,10 @@ export class RawGroup<
234
217
 
235
218
  /** @internal */
236
219
  removeMemberInternal(
237
- account: RawAccount | ControlledAccountOrAgent | AgentID | Everyone
220
+ account: RawAccount | ControlledAccountOrAgent | AgentID | Everyone,
238
221
  ) {
239
222
  const memberKey = typeof account === "string" ? account : account.id;
240
- this.set(memberKey, "revoked", "trusting");
223
+ this.set(memberKey, "revoked", "trusting");
241
224
  this.rotateReadKey();
242
225
  }
243
226
 
@@ -249,10 +232,11 @@ export class RawGroup<
249
232
  * @category 2. Role changing
250
233
  */
251
234
  createInvite(role: "reader" | "writer" | "admin"): InviteSecret {
252
- const secretSeed = newRandomSecretSeed();
235
+ const secretSeed = this.core.crypto.newRandomSecretSeed();
253
236
 
254
- const inviteSecret = agentSecretFromSecretSeed(secretSeed);
255
- const inviteID = getAgentID(inviteSecret);
237
+ const inviteSecret =
238
+ this.core.crypto.agentSecretFromSecretSeed(secretSeed);
239
+ const inviteID = this.core.crypto.getAgentID(inviteSecret);
256
240
 
257
241
  this.addMemberInternal(inviteID, `${role}Invite` as Role);
258
242
 
@@ -268,7 +252,7 @@ export class RawGroup<
268
252
  createMap<M extends RawCoMap>(
269
253
  init?: M["_shape"],
270
254
  meta?: M["headerMeta"],
271
- initPrivacy: "trusting" | "private" = "private"
255
+ initPrivacy: "trusting" | "private" = "private",
272
256
  ): M {
273
257
  const map = this.core.node
274
258
  .createCoValue({
@@ -278,7 +262,7 @@ export class RawGroup<
278
262
  group: this.id,
279
263
  },
280
264
  meta: meta || null,
281
- ...createdNowUnique(),
265
+ ...this.core.crypto.createdNowUnique(),
282
266
  })
283
267
  .getCurrentContent() as M;
284
268
 
@@ -300,7 +284,7 @@ export class RawGroup<
300
284
  createList<L extends RawCoList>(
301
285
  init?: L["_item"][],
302
286
  meta?: L["headerMeta"],
303
- initPrivacy: "trusting" | "private" = "private"
287
+ initPrivacy: "trusting" | "private" = "private",
304
288
  ): L {
305
289
  const list = this.core.node
306
290
  .createCoValue({
@@ -310,7 +294,7 @@ export class RawGroup<
310
294
  group: this.id,
311
295
  },
312
296
  meta: meta || null,
313
- ...createdNowUnique(),
297
+ ...this.core.crypto.createdNowUnique(),
314
298
  })
315
299
  .getCurrentContent() as L;
316
300
 
@@ -333,14 +317,14 @@ export class RawGroup<
333
317
  group: this.id,
334
318
  },
335
319
  meta: meta || null,
336
- ...createdNowUnique(),
320
+ ...this.core.crypto.createdNowUnique(),
337
321
  })
338
322
  .getCurrentContent() as C;
339
323
  }
340
324
 
341
325
  /** @category 3. Value creation */
342
326
  createBinaryStream<C extends RawBinaryCoStream>(
343
- meta: C["headerMeta"] = { type: "binary" }
327
+ meta: C["headerMeta"] = { type: "binary" },
344
328
  ): C {
345
329
  return this.core.node
346
330
  .createCoValue({
@@ -350,7 +334,7 @@ export class RawGroup<
350
334
  group: this.id,
351
335
  },
352
336
  meta: meta,
353
- ...createdNowUnique(),
337
+ ...this.core.crypto.createdNowUnique(),
354
338
  })
355
339
  .getCurrentContent() as C;
356
340
  }
@@ -8,7 +8,7 @@ import { RawBinaryCoStream } from "./coValues/coStream.js";
8
8
 
9
9
  export function coreToCoValue(
10
10
  core: CoValueCore,
11
- options?: { ignorePrivateTransactions: true }
11
+ options?: { ignorePrivateTransactions: true },
12
12
  ) {
13
13
  if (core.header.type === "comap") {
14
14
  if (core.header.ruleset.type === "group") {
@@ -17,7 +17,10 @@ export function coreToCoValue(
17
17
  !options?.ignorePrivateTransactions
18
18
  ) {
19
19
  if (core.id === core.node.account.id) {
20
- return new RawControlledAccount(core, core.node.account.agentSecret);
20
+ return new RawControlledAccount(
21
+ core,
22
+ core.node.account.agentSecret,
23
+ );
21
24
  } else {
22
25
  return new RawAccount(core);
23
26
  }
@@ -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
+ }