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.
- package/.eslintrc.cjs +3 -2
- package/.prettierrc.js +9 -0
- package/.turbo/turbo-build.log +3 -30
- package/.turbo/turbo-lint.log +4 -0
- package/.turbo/turbo-test.log +1106 -0
- package/CHANGELOG.md +104 -0
- package/README.md +3 -1
- package/dist/base64url.test.js +25 -0
- package/dist/base64url.test.js.map +1 -0
- package/dist/coValueCore.js +60 -37
- package/dist/coValueCore.js.map +1 -1
- package/dist/coValues/account.js +16 -15
- package/dist/coValues/account.js.map +1 -1
- package/dist/coValues/coList.js +1 -1
- package/dist/coValues/coList.js.map +1 -1
- package/dist/coValues/coMap.js +17 -8
- package/dist/coValues/coMap.js.map +1 -1
- package/dist/coValues/group.js +13 -14
- package/dist/coValues/group.js.map +1 -1
- package/dist/coreToCoValue.js.map +1 -1
- package/dist/crypto/PureJSCrypto.js +89 -0
- package/dist/crypto/PureJSCrypto.js.map +1 -0
- package/dist/crypto/WasmCrypto.js +127 -0
- package/dist/crypto/WasmCrypto.js.map +1 -0
- package/dist/crypto/crypto.js +151 -0
- package/dist/crypto/crypto.js.map +1 -0
- package/dist/ids.js +4 -2
- package/dist/ids.js.map +1 -1
- package/dist/index.js +6 -8
- package/dist/index.js.map +1 -1
- package/dist/jsonStringify.js.map +1 -1
- package/dist/localNode.js +41 -38
- package/dist/localNode.js.map +1 -1
- package/dist/permissions.js +6 -6
- package/dist/permissions.js.map +1 -1
- package/dist/storage/FileSystem.js +61 -0
- package/dist/storage/FileSystem.js.map +1 -0
- package/dist/storage/chunksAndKnownStates.js +97 -0
- package/dist/storage/chunksAndKnownStates.js.map +1 -0
- package/dist/storage/index.js +265 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/sync.js +29 -25
- package/dist/sync.js.map +1 -1
- package/dist/tests/account.test.js +58 -0
- package/dist/tests/account.test.js.map +1 -0
- package/dist/tests/coList.test.js +76 -0
- package/dist/tests/coList.test.js.map +1 -0
- package/dist/tests/coMap.test.js +136 -0
- package/dist/tests/coMap.test.js.map +1 -0
- package/dist/tests/coStream.test.js +172 -0
- package/dist/tests/coStream.test.js.map +1 -0
- package/dist/tests/coValueCore.test.js +114 -0
- package/dist/tests/coValueCore.test.js.map +1 -0
- package/dist/tests/crypto.test.js +118 -0
- package/dist/tests/crypto.test.js.map +1 -0
- package/dist/tests/cryptoImpl.test.js +113 -0
- package/dist/tests/cryptoImpl.test.js.map +1 -0
- package/dist/tests/group.test.js +34 -0
- package/dist/tests/group.test.js.map +1 -0
- package/dist/tests/permissions.test.js +1060 -0
- package/dist/tests/permissions.test.js.map +1 -0
- package/dist/tests/sync.test.js +816 -0
- package/dist/tests/sync.test.js.map +1 -0
- package/dist/tests/testUtils.js +12 -11
- package/dist/tests/testUtils.js.map +1 -1
- package/dist/typeUtils/accountOrAgentIDfromSessionID.js.map +1 -1
- package/dist/typeUtils/isAccountID.js.map +1 -1
- package/dist/typeUtils/isCoValue.js.map +1 -1
- package/package.json +14 -27
- package/src/base64url.test.ts +6 -5
- package/src/coValue.ts +1 -1
- package/src/coValueCore.ts +179 -126
- package/src/coValues/account.ts +30 -32
- package/src/coValues/coList.ts +11 -11
- package/src/coValues/coMap.ts +27 -17
- package/src/coValues/coStream.ts +17 -17
- package/src/coValues/group.ts +93 -109
- package/src/coreToCoValue.ts +5 -2
- package/src/crypto/PureJSCrypto.ts +200 -0
- package/src/crypto/WasmCrypto.ts +259 -0
- package/src/crypto/crypto.ts +336 -0
- package/src/ids.ts +8 -7
- package/src/index.ts +24 -24
- package/src/jsonStringify.ts +6 -4
- package/src/jsonValue.ts +2 -2
- package/src/localNode.ts +103 -109
- package/src/media.ts +3 -3
- package/src/permissions.ts +19 -21
- package/src/storage/FileSystem.ts +152 -0
- package/src/storage/chunksAndKnownStates.ts +139 -0
- package/src/storage/index.ts +479 -0
- package/src/streamUtils.ts +12 -12
- package/src/sync.ts +79 -63
- package/src/tests/account.test.ts +15 -15
- package/src/tests/coList.test.ts +94 -0
- package/src/tests/coMap.test.ts +162 -0
- package/src/tests/coStream.test.ts +246 -0
- package/src/tests/coValueCore.test.ts +36 -37
- package/src/tests/crypto.test.ts +66 -72
- package/src/tests/cryptoImpl.test.ts +183 -0
- package/src/tests/group.test.ts +16 -17
- package/src/tests/permissions.test.ts +269 -283
- package/src/tests/sync.test.ts +122 -123
- package/src/tests/testUtils.ts +24 -21
- package/src/typeUtils/accountOrAgentIDfromSessionID.ts +1 -2
- package/src/typeUtils/expectGroup.ts +1 -1
- package/src/typeUtils/isAccountID.ts +0 -1
- package/src/typeUtils/isCoValue.ts +1 -2
- package/tsconfig.json +0 -1
- package/dist/crypto.js +0 -254
- package/dist/crypto.js.map +0 -1
- package/src/crypto.ts +0 -484
- package/src/tests/coValue.test.ts +0 -497
package/src/coValues/group.ts
CHANGED
|
@@ -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
|
-
|
|
94
|
+
const currentReadKey = this.core.getCurrentReadKey();
|
|
112
95
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
96
|
+
if (!currentReadKey.secret) {
|
|
97
|
+
throw new Error("Can't add member without read key secret");
|
|
98
|
+
}
|
|
116
99
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
-
`${
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
255
|
-
|
|
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
|
}
|
package/src/coreToCoValue.ts
CHANGED
|
@@ -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(
|
|
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
|
+
}
|