@dxos/credentials 0.10.0 → 0.11.1
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/CHANGELOG.md +43 -0
- package/dist/lib/index.mjs +1687 -0
- package/dist/lib/index.mjs.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/moon.yml +1 -7
- package/package.json +13 -13
- package/vite.config.ts +9 -0
- package/dist/lib/neutral/index.mjs +0 -1684
- package/dist/lib/neutral/index.mjs.map +0 -7
- package/dist/lib/neutral/meta.json +0 -1
- package/vitest.config.ts +0 -13
|
@@ -0,0 +1,1687 @@
|
|
|
1
|
+
import { createKeyPair, subtleCrypto, verifySignature } from "@dxos/crypto";
|
|
2
|
+
import { IdentityDid, PublicKey } from "@dxos/keys";
|
|
3
|
+
import { Callback, ComplexMap, ComplexSet, arrayToBuffer } from "@dxos/util";
|
|
4
|
+
import { generateMnemonic, mnemonicToSeedSync } from "bip39";
|
|
5
|
+
import { invariant } from "@dxos/invariant";
|
|
6
|
+
import stableStringify from "json-stable-stringify";
|
|
7
|
+
import { Timeframe } from "@dxos/timeframe";
|
|
8
|
+
import { AdmittedFeed, MembershipPolicy, SpaceMember } from "@dxos/protocols/proto/dxos/halo/credentials";
|
|
9
|
+
import { Trigger, runInContextAsync, synchronized } from "@dxos/async";
|
|
10
|
+
import { Context } from "@dxos/context";
|
|
11
|
+
import { log } from "@dxos/log";
|
|
12
|
+
//#region src/did.ts
|
|
13
|
+
var IDENTITY_DIDS_CACHE = new ComplexMap(PublicKey.hash);
|
|
14
|
+
/**
|
|
15
|
+
* Identity DIDs are generated by creating a keypair, and then taking the first 20 bytes of the SHA-256 hash of the public key and encoding them to multibase RFC4648 base-32 format (prefixed with B, see Multibase Table).
|
|
16
|
+
* Inspired by how ethereum addresses are derived.
|
|
17
|
+
*/
|
|
18
|
+
var createDidFromIdentityKey = async (identityKey) => {
|
|
19
|
+
const cachedValue = IDENTITY_DIDS_CACHE.get(identityKey);
|
|
20
|
+
if (cachedValue !== void 0) return cachedValue;
|
|
21
|
+
const digest = await subtleCrypto.digest("SHA-256", identityKey.asUint8Array());
|
|
22
|
+
const bytes = new Uint8Array(digest).slice(0, IdentityDid.byteLength);
|
|
23
|
+
const identityDid = IdentityDid.encode(bytes);
|
|
24
|
+
IDENTITY_DIDS_CACHE.set(identityKey, identityDid);
|
|
25
|
+
return identityDid;
|
|
26
|
+
};
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region src/invitations.ts
|
|
29
|
+
var defaultSecretProvider = async () => Buffer.from("0000");
|
|
30
|
+
var defaultSecretValidator = async (invitation, secret) => true;
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region src/seedphrase.ts
|
|
33
|
+
var __dxlog_file$7 = "/__w/dxos/dxos/packages/core/halo/credentials/src/seedphrase.ts";
|
|
34
|
+
/**
|
|
35
|
+
* Generate bip39 seed phrase (aka mnemonic).
|
|
36
|
+
*/
|
|
37
|
+
var generateSeedPhrase = () => generateMnemonic();
|
|
38
|
+
/**
|
|
39
|
+
* Generate key pair from seed phrase.
|
|
40
|
+
*/
|
|
41
|
+
var keyPairFromSeedPhrase = (seedPhrase) => {
|
|
42
|
+
invariant(seedPhrase, void 0, {
|
|
43
|
+
"~LogMeta": "~LogMeta",
|
|
44
|
+
F: __dxlog_file$7,
|
|
45
|
+
L: 20,
|
|
46
|
+
S: void 0,
|
|
47
|
+
A: ["seedPhrase", ""]
|
|
48
|
+
});
|
|
49
|
+
return createKeyPair(mnemonicToSeedSync(seedPhrase));
|
|
50
|
+
};
|
|
51
|
+
//#endregion
|
|
52
|
+
//#region src/passcode.ts
|
|
53
|
+
/**
|
|
54
|
+
* Generates a numeric passcode.
|
|
55
|
+
* @param {number} length
|
|
56
|
+
* @returns {string}
|
|
57
|
+
*/
|
|
58
|
+
var generatePasscode = (length = 4) => {
|
|
59
|
+
let passcode = "";
|
|
60
|
+
for (let i = 0; i < length; i++) passcode += `${Math.floor(Math.random() * 10)}`;
|
|
61
|
+
return passcode;
|
|
62
|
+
};
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region src/credentials/signing.ts
|
|
65
|
+
/**
|
|
66
|
+
* @returns The input message to be signed for a given credential.
|
|
67
|
+
*/
|
|
68
|
+
var getCredentialProofPayload = (credential) => {
|
|
69
|
+
const copy = {
|
|
70
|
+
...credential,
|
|
71
|
+
proof: {
|
|
72
|
+
...credential.proof,
|
|
73
|
+
value: /* @__PURE__ */ new Uint8Array(),
|
|
74
|
+
chain: void 0
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
if (copy.parentCredentialIds?.length === 0) delete copy.parentCredentialIds;
|
|
78
|
+
delete copy.id;
|
|
79
|
+
const originalAssertion = copy.subject?.assertion;
|
|
80
|
+
if (originalAssertion) {
|
|
81
|
+
const normalizedAssertion = { ...originalAssertion };
|
|
82
|
+
for (const key of Object.keys(normalizedAssertion)) {
|
|
83
|
+
const val = normalizedAssertion[key];
|
|
84
|
+
if (val === void 0 || val === null || val === 0 || val === "" || val === false || Array.isArray(val) && val.length === 0) delete normalizedAssertion[key];
|
|
85
|
+
}
|
|
86
|
+
copy.subject = {
|
|
87
|
+
...copy.subject,
|
|
88
|
+
assertion: normalizedAssertion
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
return Buffer.from(canonicalStringify(copy));
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Utility method to produce stable output for signing/verifying.
|
|
95
|
+
*/
|
|
96
|
+
var canonicalStringify = (obj) => stableStringify(obj, { replacer: function(key, value) {
|
|
97
|
+
if (key.toString().startsWith("__") || key.toString() === "@type") return;
|
|
98
|
+
if (value === null) return;
|
|
99
|
+
const original = this[key];
|
|
100
|
+
if (value) {
|
|
101
|
+
if (PublicKey.isPublicKey(value)) return value.toHex();
|
|
102
|
+
if (Buffer.isBuffer(value)) return value.toString("hex");
|
|
103
|
+
if (value instanceof Uint8Array) return arrayToBuffer(value).toString("hex");
|
|
104
|
+
if (value.data && value.type === "Buffer") return Buffer.from(value).toString("hex");
|
|
105
|
+
if (original instanceof Timeframe) return original.frames().reduce((frames, [key, seq]) => {
|
|
106
|
+
frames[truncateKey(key)] = seq;
|
|
107
|
+
return frames;
|
|
108
|
+
}, {});
|
|
109
|
+
}
|
|
110
|
+
return value;
|
|
111
|
+
} });
|
|
112
|
+
/**
|
|
113
|
+
* Old key truncation method (339d...9d66) to keep backwards compatibility with credentials signed with old method
|
|
114
|
+
*/
|
|
115
|
+
var truncateKey = (key) => {
|
|
116
|
+
const str = key.toHex();
|
|
117
|
+
return `${str.substring(0, 4)}...${str.substring(str.length - 4)}`;
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
120
|
+
* export const truncateKey = (key: any, { length = 8, start }: TruncateKeyOptions = {}) => {
|
|
121
|
+
const str = String(key);
|
|
122
|
+
if (str.length <= length) {
|
|
123
|
+
return str;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return start
|
|
127
|
+
? `${str.slice(0, length)}...`
|
|
128
|
+
: `${str.substring(0, length / 2)}...${str.substring(str.length - length / 2)}`;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
{
|
|
132
|
+
"04009285": 20,
|
|
133
|
+
"0415004f": 0,
|
|
134
|
+
"0415e6d7": 9964,
|
|
135
|
+
"042a4fa9": 8,
|
|
136
|
+
"0448e62f": 3,
|
|
137
|
+
"04775053": 257,
|
|
138
|
+
"04a6b603": 97,
|
|
139
|
+
"04bc5c9d": 198,
|
|
140
|
+
"04da9930": 59,
|
|
141
|
+
"04df0449": 676,
|
|
142
|
+
"04e122ae": 5435,
|
|
143
|
+
"04ee588b": 1703
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
*/
|
|
147
|
+
//#endregion
|
|
148
|
+
//#region src/presentations/signing.ts
|
|
149
|
+
var getPresentationProofPayload = (credentials, proof) => {
|
|
150
|
+
const copy = {
|
|
151
|
+
credentials: credentials.map((credential) => removeEmptyParentCredentialIds(credential)),
|
|
152
|
+
proof: {
|
|
153
|
+
...proof,
|
|
154
|
+
value: /* @__PURE__ */ new Uint8Array(),
|
|
155
|
+
chain: void 0
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
return Buffer.from(canonicalStringify(copy));
|
|
159
|
+
};
|
|
160
|
+
var removeEmptyParentCredentialIds = (credential) => {
|
|
161
|
+
const copy = {
|
|
162
|
+
...credential,
|
|
163
|
+
proof: credential.proof ? {
|
|
164
|
+
...credential.proof,
|
|
165
|
+
chain: credential.proof.chain ? { credential: removeEmptyParentCredentialIds(credential.proof.chain.credential) } : void 0
|
|
166
|
+
} : void 0
|
|
167
|
+
};
|
|
168
|
+
if (copy.parentCredentialIds?.length === 0) delete copy.parentCredentialIds;
|
|
169
|
+
return copy;
|
|
170
|
+
};
|
|
171
|
+
//#endregion
|
|
172
|
+
//#region src/credentials/assertions.ts
|
|
173
|
+
var getCredentialAssertion = (credential) => credential.subject.assertion;
|
|
174
|
+
var isValidAuthorizedDeviceCredential = (credential, identityKey, deviceKey) => {
|
|
175
|
+
const assertion = getCredentialAssertion(credential);
|
|
176
|
+
return credential.subject.id.equals(deviceKey) && credential.issuer.equals(identityKey) && assertion["@type"] === "dxos.halo.credentials.AuthorizedDevice" && assertion.identityKey.equals(identityKey) && assertion.deviceKey.equals(deviceKey);
|
|
177
|
+
};
|
|
178
|
+
var checkCredentialType = (credential, type) => credential.subject.assertion["@type"] === type;
|
|
179
|
+
var credentialTypeFilter = (type) => (credential) => checkCredentialType(credential, type);
|
|
180
|
+
//#endregion
|
|
181
|
+
//#region src/credentials/verifier.ts
|
|
182
|
+
var SIGNATURE_TYPE_ED25519 = "ED25519Signature";
|
|
183
|
+
var verifyCredential = async (credential) => {
|
|
184
|
+
if (credential.parentCredentialIds?.length === 0) delete credential.parentCredentialIds;
|
|
185
|
+
if (!credential.issuer.equals(credential.proof.signer)) {
|
|
186
|
+
if (!credential.proof.chain) return {
|
|
187
|
+
kind: "fail",
|
|
188
|
+
errors: ["Delegated credential is missing credential chain."]
|
|
189
|
+
};
|
|
190
|
+
const result = await verifyChain(credential.proof.chain, credential.issuer, credential.proof.signer);
|
|
191
|
+
if (result.kind === "fail") return result;
|
|
192
|
+
}
|
|
193
|
+
const result = await verifyCredentialSignature(credential);
|
|
194
|
+
if (result.kind === "fail") return result;
|
|
195
|
+
return { kind: "pass" };
|
|
196
|
+
};
|
|
197
|
+
/**
|
|
198
|
+
* Verifies that the signature is valid and was made by the signer.
|
|
199
|
+
* Does not validate other semantics (e.g. chains).
|
|
200
|
+
*/
|
|
201
|
+
var verifyCredentialSignature = async (credential) => {
|
|
202
|
+
if (credential.proof.type !== "ED25519Signature") return {
|
|
203
|
+
kind: "fail",
|
|
204
|
+
errors: [`Invalid signature type: ${credential.proof.type}`]
|
|
205
|
+
};
|
|
206
|
+
const signData = getCredentialProofPayload(credential);
|
|
207
|
+
if (!await verifySignature(credential.proof.signer, signData, credential.proof.value)) return {
|
|
208
|
+
kind: "fail",
|
|
209
|
+
errors: ["Invalid signature"]
|
|
210
|
+
};
|
|
211
|
+
return { kind: "pass" };
|
|
212
|
+
};
|
|
213
|
+
/**
|
|
214
|
+
* Verifies that the signer has the delegated authority to create credentials on behalf of the issuer.
|
|
215
|
+
*/
|
|
216
|
+
var verifyChain = async (chain, authority, subject) => {
|
|
217
|
+
const result = await verifyCredential(chain.credential);
|
|
218
|
+
if (result.kind === "fail") return result;
|
|
219
|
+
if (!isValidAuthorizedDeviceCredential(chain.credential, authority, subject)) return {
|
|
220
|
+
kind: "fail",
|
|
221
|
+
errors: [`Invalid credential chain: invalid assertion for key: ${subject}`]
|
|
222
|
+
};
|
|
223
|
+
return { kind: "pass" };
|
|
224
|
+
};
|
|
225
|
+
//#endregion
|
|
226
|
+
//#region src/credentials/credential-factory.ts
|
|
227
|
+
var __dxlog_file$6 = "/__w/dxos/dxos/packages/core/halo/credentials/src/credentials/credential-factory.ts";
|
|
228
|
+
/**
|
|
229
|
+
* Construct a signed credential message.
|
|
230
|
+
*/
|
|
231
|
+
var createCredential = async ({ signer, issuer, subject, assertion, signingKey, chain, nonce, parentCredentialIds }) => {
|
|
232
|
+
invariant(assertion["@type"], "Invalid assertion.", {
|
|
233
|
+
"~LogMeta": "~LogMeta",
|
|
234
|
+
F: __dxlog_file$6,
|
|
235
|
+
L: 48,
|
|
236
|
+
S: void 0,
|
|
237
|
+
A: ["assertion['@type']", "'Invalid assertion.'"]
|
|
238
|
+
});
|
|
239
|
+
invariant(!!signingKey === !!chain, "Chain must be provided if and only if the signing key differs from the issuer.", {
|
|
240
|
+
"~LogMeta": "~LogMeta",
|
|
241
|
+
F: __dxlog_file$6,
|
|
242
|
+
L: 49,
|
|
243
|
+
S: void 0,
|
|
244
|
+
A: ["!!signingKey === !!chain", "'Chain must be provided if and only if the signing key differs from the issuer.'"]
|
|
245
|
+
});
|
|
246
|
+
if (chain) invariant((await verifyChain(chain, issuer, signingKey)).kind === "pass", "Invalid chain.", {
|
|
247
|
+
"~LogMeta": "~LogMeta",
|
|
248
|
+
F: __dxlog_file$6,
|
|
249
|
+
L: 52,
|
|
250
|
+
S: void 0,
|
|
251
|
+
A: ["result.kind === 'pass'", "'Invalid chain.'"]
|
|
252
|
+
});
|
|
253
|
+
const credential = {
|
|
254
|
+
issuer,
|
|
255
|
+
issuanceDate: /* @__PURE__ */ new Date(),
|
|
256
|
+
subject: {
|
|
257
|
+
id: subject,
|
|
258
|
+
assertion
|
|
259
|
+
},
|
|
260
|
+
parentCredentialIds,
|
|
261
|
+
proof: {
|
|
262
|
+
type: SIGNATURE_TYPE_ED25519,
|
|
263
|
+
creationDate: /* @__PURE__ */ new Date(),
|
|
264
|
+
signer: signingKey ?? issuer,
|
|
265
|
+
value: /* @__PURE__ */ new Uint8Array(),
|
|
266
|
+
nonce
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
const signedPayload = getCredentialProofPayload(credential);
|
|
270
|
+
credential.proof.value = await signer.sign(signingKey ?? issuer, signedPayload);
|
|
271
|
+
if (chain) credential.proof.chain = chain;
|
|
272
|
+
credential.id = PublicKey.from(await subtleCrypto.digest("SHA-256", signedPayload));
|
|
273
|
+
return credential;
|
|
274
|
+
};
|
|
275
|
+
var createCredentialMessage = (credential) => {
|
|
276
|
+
return {
|
|
277
|
+
"@type": "dxos.echo.feed.CredentialsMessage",
|
|
278
|
+
credential
|
|
279
|
+
};
|
|
280
|
+
};
|
|
281
|
+
/**
|
|
282
|
+
* Issue credentials directly signed by the issuer.
|
|
283
|
+
*/
|
|
284
|
+
var createCredentialSignerWithKey = (signer, issuer) => ({
|
|
285
|
+
getIssuer: () => issuer,
|
|
286
|
+
createCredential: ({ subject, assertion, nonce, parentCredentialIds }) => createCredential({
|
|
287
|
+
signer,
|
|
288
|
+
issuer,
|
|
289
|
+
subject,
|
|
290
|
+
assertion,
|
|
291
|
+
nonce,
|
|
292
|
+
parentCredentialIds
|
|
293
|
+
})
|
|
294
|
+
});
|
|
295
|
+
/**
|
|
296
|
+
* Issue credentials with transitive proof via a chain.
|
|
297
|
+
*/
|
|
298
|
+
var createCredentialSignerWithChain = (signer, chain, signingKey) => ({
|
|
299
|
+
getIssuer: () => chain.credential.issuer,
|
|
300
|
+
createCredential: ({ subject, assertion, nonce, parentCredentialIds }) => createCredential({
|
|
301
|
+
signer,
|
|
302
|
+
issuer: chain.credential.issuer,
|
|
303
|
+
signingKey,
|
|
304
|
+
chain,
|
|
305
|
+
subject,
|
|
306
|
+
assertion,
|
|
307
|
+
nonce,
|
|
308
|
+
parentCredentialIds
|
|
309
|
+
})
|
|
310
|
+
});
|
|
311
|
+
//#endregion
|
|
312
|
+
//#region src/credentials/credential-generator.ts
|
|
313
|
+
/**
|
|
314
|
+
* Utility class for generating credential messages, where the issuer is the current identity or device.
|
|
315
|
+
*/
|
|
316
|
+
var CredentialGenerator = class {
|
|
317
|
+
_signer;
|
|
318
|
+
_identityKey;
|
|
319
|
+
_deviceKey;
|
|
320
|
+
constructor(_signer, _identityKey, _deviceKey) {
|
|
321
|
+
this._signer = _signer;
|
|
322
|
+
this._identityKey = _identityKey;
|
|
323
|
+
this._deviceKey = _deviceKey;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Create genesis messages for new Space.
|
|
327
|
+
*/
|
|
328
|
+
async createSpaceGenesis(spaceKey, controlKey, creatorProfile, membershipPolicy = MembershipPolicy.INVITE) {
|
|
329
|
+
return [
|
|
330
|
+
await createCredential({
|
|
331
|
+
signer: this._signer,
|
|
332
|
+
issuer: spaceKey,
|
|
333
|
+
subject: spaceKey,
|
|
334
|
+
assertion: {
|
|
335
|
+
"@type": "dxos.halo.credentials.SpaceGenesis",
|
|
336
|
+
spaceKey,
|
|
337
|
+
membershipPolicy
|
|
338
|
+
}
|
|
339
|
+
}),
|
|
340
|
+
await createCredential({
|
|
341
|
+
signer: this._signer,
|
|
342
|
+
issuer: spaceKey,
|
|
343
|
+
subject: this._identityKey,
|
|
344
|
+
assertion: {
|
|
345
|
+
"@type": "dxos.halo.credentials.SpaceMember",
|
|
346
|
+
spaceKey,
|
|
347
|
+
"role": SpaceMember.Role.ADMIN,
|
|
348
|
+
"profile": creatorProfile,
|
|
349
|
+
"genesisFeedKey": controlKey
|
|
350
|
+
}
|
|
351
|
+
}),
|
|
352
|
+
await this.createFeedAdmission(spaceKey, controlKey, AdmittedFeed.Designation.CONTROL)
|
|
353
|
+
];
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Create invitation.
|
|
357
|
+
* Admit identity and control and data feeds.
|
|
358
|
+
*/
|
|
359
|
+
async createMemberInvitation(spaceKey, identityKey, deviceKey, controlKey, dataKey, genesisFeedKey) {
|
|
360
|
+
return [
|
|
361
|
+
await createCredential({
|
|
362
|
+
signer: this._signer,
|
|
363
|
+
issuer: this._identityKey,
|
|
364
|
+
subject: identityKey,
|
|
365
|
+
assertion: {
|
|
366
|
+
"@type": "dxos.halo.credentials.SpaceMember",
|
|
367
|
+
spaceKey,
|
|
368
|
+
"role": SpaceMember.Role.EDITOR,
|
|
369
|
+
genesisFeedKey
|
|
370
|
+
}
|
|
371
|
+
}),
|
|
372
|
+
await this.createFeedAdmission(spaceKey, controlKey, AdmittedFeed.Designation.CONTROL),
|
|
373
|
+
await this.createFeedAdmission(spaceKey, dataKey, AdmittedFeed.Designation.DATA)
|
|
374
|
+
];
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Add device to space.
|
|
378
|
+
*/
|
|
379
|
+
async createDeviceAuthorization(deviceKey) {
|
|
380
|
+
return createCredential({
|
|
381
|
+
signer: this._signer,
|
|
382
|
+
issuer: this._identityKey,
|
|
383
|
+
subject: deviceKey,
|
|
384
|
+
assertion: {
|
|
385
|
+
"@type": "dxos.halo.credentials.AuthorizedDevice",
|
|
386
|
+
"identityKey": this._identityKey,
|
|
387
|
+
deviceKey
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Add device metadata.
|
|
393
|
+
*/
|
|
394
|
+
async createDeviceProfile(profile) {
|
|
395
|
+
return createCredential({
|
|
396
|
+
signer: this._signer,
|
|
397
|
+
issuer: this._identityKey,
|
|
398
|
+
subject: this._deviceKey,
|
|
399
|
+
assertion: {
|
|
400
|
+
"@type": "dxos.halo.credentials.DeviceProfile",
|
|
401
|
+
profile
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Add feed to space.
|
|
407
|
+
*/
|
|
408
|
+
async createFeedAdmission(spaceKey, feedKey, designation) {
|
|
409
|
+
return createCredential({
|
|
410
|
+
signer: this._signer,
|
|
411
|
+
issuer: this._identityKey,
|
|
412
|
+
subject: feedKey,
|
|
413
|
+
assertion: {
|
|
414
|
+
"@type": "dxos.halo.credentials.AdmittedFeed",
|
|
415
|
+
spaceKey,
|
|
416
|
+
"identityKey": this._identityKey,
|
|
417
|
+
"deviceKey": this._deviceKey,
|
|
418
|
+
designation
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
async createProfileCredential(profile) {
|
|
423
|
+
return createCredential({
|
|
424
|
+
signer: this._signer,
|
|
425
|
+
issuer: this._identityKey,
|
|
426
|
+
subject: this._identityKey,
|
|
427
|
+
assertion: {
|
|
428
|
+
"@type": "dxos.halo.credentials.IdentityProfile",
|
|
429
|
+
profile
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
async createEpochCredential(spaceKey) {
|
|
434
|
+
return createCredential({
|
|
435
|
+
signer: this._signer,
|
|
436
|
+
issuer: this._identityKey,
|
|
437
|
+
subject: spaceKey,
|
|
438
|
+
assertion: {
|
|
439
|
+
"@type": "dxos.halo.credentials.Epoch",
|
|
440
|
+
"number": 0,
|
|
441
|
+
"timeframe": new Timeframe()
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
var createDeviceAuthorization = async (signer, identityKey, deviceKey) => {
|
|
447
|
+
return (await Promise.all([await signer.createCredential({
|
|
448
|
+
subject: deviceKey,
|
|
449
|
+
assertion: {
|
|
450
|
+
"@type": "dxos.halo.credentials.AuthorizedDevice",
|
|
451
|
+
identityKey,
|
|
452
|
+
deviceKey
|
|
453
|
+
}
|
|
454
|
+
})])).map((credential) => ({
|
|
455
|
+
"@type": "dxos.echo.feed.CredentialsMessage",
|
|
456
|
+
credential
|
|
457
|
+
}));
|
|
458
|
+
};
|
|
459
|
+
/**
|
|
460
|
+
* @param signer - invitation signer.
|
|
461
|
+
* @param identityKey - identity key of the admitted member.
|
|
462
|
+
* @param spaceKey - subject space key.
|
|
463
|
+
* @param genesisFeedKey - genesis feed key of the space.
|
|
464
|
+
* @param role - role of the newly added member.
|
|
465
|
+
* @param membershipChainHeads - ids of the last known SpaceMember credentials (branching possible).
|
|
466
|
+
* @param profile - profile of the newly added member.
|
|
467
|
+
* @param invitationCredentialId - id of the delegated invitation credential in case one was used to add the member.
|
|
468
|
+
*/
|
|
469
|
+
var createAdmissionCredentials = async (signer, identityKey, spaceKey, genesisFeedKey, role = SpaceMember.Role.ADMIN, membershipChainHeads = [], profile, invitationCredentialId, tags) => {
|
|
470
|
+
return (await Promise.all([await signer.createCredential({
|
|
471
|
+
subject: identityKey,
|
|
472
|
+
parentCredentialIds: membershipChainHeads,
|
|
473
|
+
assertion: {
|
|
474
|
+
"@type": "dxos.halo.credentials.SpaceMember",
|
|
475
|
+
spaceKey,
|
|
476
|
+
role,
|
|
477
|
+
profile,
|
|
478
|
+
genesisFeedKey,
|
|
479
|
+
invitationCredentialId,
|
|
480
|
+
"tags": tags ?? []
|
|
481
|
+
}
|
|
482
|
+
})])).map((credential) => ({ credential: { credential } }));
|
|
483
|
+
};
|
|
484
|
+
var createDelegatedSpaceInvitationCredential = async (signer, subject, invitation) => {
|
|
485
|
+
return { credential: { credential: await signer.createCredential({
|
|
486
|
+
subject,
|
|
487
|
+
assertion: {
|
|
488
|
+
"@type": "dxos.halo.invitations.DelegateSpaceInvitation",
|
|
489
|
+
"invitationId": invitation.invitationId,
|
|
490
|
+
"authMethod": invitation.authMethod,
|
|
491
|
+
"swarmKey": invitation.swarmKey,
|
|
492
|
+
"role": invitation.role,
|
|
493
|
+
"guestKey": invitation.guestKey,
|
|
494
|
+
"expiresOn": invitation.expiresOn,
|
|
495
|
+
"multiUse": invitation.multiUse
|
|
496
|
+
}
|
|
497
|
+
}) } };
|
|
498
|
+
};
|
|
499
|
+
/**
|
|
500
|
+
* @param signer - credential issuer.
|
|
501
|
+
* @param subject - key of the space the invitation was for.
|
|
502
|
+
* @param invitationCredentialId id of a dxos.halo.invitations.DelegateSpaceInvitation credential.
|
|
503
|
+
*/
|
|
504
|
+
var createCancelDelegatedSpaceInvitationCredential = async (signer, subject, invitationCredentialId) => {
|
|
505
|
+
return { credential: { credential: await signer.createCredential({
|
|
506
|
+
subject,
|
|
507
|
+
assertion: {
|
|
508
|
+
"@type": "dxos.halo.invitations.CancelDelegatedInvitation",
|
|
509
|
+
"credentialId": invitationCredentialId
|
|
510
|
+
}
|
|
511
|
+
}) } };
|
|
512
|
+
};
|
|
513
|
+
//#endregion
|
|
514
|
+
//#region src/presentations/presentation.ts
|
|
515
|
+
var signPresentation = async ({ presentation, signer, signerKey, chain, nonce }) => {
|
|
516
|
+
const proof = {
|
|
517
|
+
type: SIGNATURE_TYPE_ED25519,
|
|
518
|
+
value: /* @__PURE__ */ new Uint8Array(),
|
|
519
|
+
creationDate: /* @__PURE__ */ new Date(),
|
|
520
|
+
signer: signerKey,
|
|
521
|
+
nonce
|
|
522
|
+
};
|
|
523
|
+
const signedPayload = getPresentationProofPayload(presentation.credentials ?? [], proof);
|
|
524
|
+
proof.value = await signer.sign(signerKey, signedPayload);
|
|
525
|
+
if (chain) proof.chain = chain;
|
|
526
|
+
return {
|
|
527
|
+
credentials: presentation.credentials,
|
|
528
|
+
proofs: [...presentation.proofs ?? [], proof]
|
|
529
|
+
};
|
|
530
|
+
};
|
|
531
|
+
//#endregion
|
|
532
|
+
//#region src/presentations/verifier.ts
|
|
533
|
+
var verifyPresentation = async (presentation) => {
|
|
534
|
+
const errors = [];
|
|
535
|
+
const credentialsVerifications = await Promise.all(presentation.credentials?.map((credential) => verifyCredential(credential)) ?? []);
|
|
536
|
+
for (const verification of credentialsVerifications) if (verification.kind === "fail") errors.push(...verification.errors);
|
|
537
|
+
const proofVerification = await Promise.all(presentation.proofs?.map(async (proof) => {
|
|
538
|
+
const chainVerification = await verifyPresentationChain(presentation, proof);
|
|
539
|
+
if (chainVerification.kind === "fail") return chainVerification;
|
|
540
|
+
const signatureVerification = await verifyPresentationSignature(presentation, proof);
|
|
541
|
+
if (signatureVerification.kind === "fail") return signatureVerification;
|
|
542
|
+
return { kind: "pass" };
|
|
543
|
+
}) ?? []);
|
|
544
|
+
for (const verification of proofVerification) if (verification.kind === "fail") errors.push(...verification.errors);
|
|
545
|
+
if (errors.length === 0) return { kind: "pass" };
|
|
546
|
+
return {
|
|
547
|
+
kind: "fail",
|
|
548
|
+
errors
|
|
549
|
+
};
|
|
550
|
+
};
|
|
551
|
+
var verifyPresentationChain = async (presentation, proof) => {
|
|
552
|
+
for (const credential of presentation.credentials ?? []) if (!credential.issuer.equals(proof.signer)) {
|
|
553
|
+
if (!proof.chain) return {
|
|
554
|
+
kind: "fail",
|
|
555
|
+
errors: ["Delegated credential is missing credential chain."]
|
|
556
|
+
};
|
|
557
|
+
const chainVerification = await verifyChain(proof.chain, credential.subject.id, proof.signer);
|
|
558
|
+
if (chainVerification.kind === "fail") return chainVerification;
|
|
559
|
+
}
|
|
560
|
+
return { kind: "pass" };
|
|
561
|
+
};
|
|
562
|
+
/**
|
|
563
|
+
* Verifies that the signature is valid and was made by the signer.
|
|
564
|
+
* Does not validate other semantics (e.g. chains).
|
|
565
|
+
*/
|
|
566
|
+
var verifyPresentationSignature = async (presentation, proof) => {
|
|
567
|
+
if (proof.type !== "ED25519Signature") return {
|
|
568
|
+
kind: "fail",
|
|
569
|
+
errors: [`Invalid signature type: ${proof.type}`]
|
|
570
|
+
};
|
|
571
|
+
const signData = getPresentationProofPayload(presentation.credentials ?? [], proof);
|
|
572
|
+
if (!await verifySignature(proof.signer, signData, proof.value)) return {
|
|
573
|
+
kind: "fail",
|
|
574
|
+
errors: ["Invalid signature"]
|
|
575
|
+
};
|
|
576
|
+
return { kind: "pass" };
|
|
577
|
+
};
|
|
578
|
+
//#endregion
|
|
579
|
+
//#region src/state-machine/feed-state-machine.ts
|
|
580
|
+
var __dxlog_file$5 = "/__w/dxos/dxos/packages/core/halo/credentials/src/state-machine/feed-state-machine.ts";
|
|
581
|
+
/**
|
|
582
|
+
* Tracks the feed tree for a space.
|
|
583
|
+
* Provides a list of admitted feeds.
|
|
584
|
+
*/
|
|
585
|
+
var FeedStateMachine = class {
|
|
586
|
+
_spaceKey;
|
|
587
|
+
_feeds = new ComplexMap(PublicKey.hash);
|
|
588
|
+
onFeedAdmitted = new Callback();
|
|
589
|
+
constructor(_spaceKey) {
|
|
590
|
+
this._spaceKey = _spaceKey;
|
|
591
|
+
}
|
|
592
|
+
get feeds() {
|
|
593
|
+
return this._feeds;
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Processes the AdmittedFeed credential.
|
|
597
|
+
* Assumes the credential is already pre-verified
|
|
598
|
+
* and the issuer has been authorized to issue credentials of this type.
|
|
599
|
+
* @param fromFeed Key of the feed where this credential is recorded.
|
|
600
|
+
*/
|
|
601
|
+
async process(credential, fromFeed) {
|
|
602
|
+
const assertion = getCredentialAssertion(credential);
|
|
603
|
+
invariant(assertion["@type"] === "dxos.halo.credentials.AdmittedFeed", void 0, {
|
|
604
|
+
"~LogMeta": "~LogMeta",
|
|
605
|
+
F: __dxlog_file$5,
|
|
606
|
+
L: 47,
|
|
607
|
+
S: this,
|
|
608
|
+
A: ["assertion['@type'] === 'dxos.halo.credentials.AdmittedFeed'", ""]
|
|
609
|
+
});
|
|
610
|
+
invariant(assertion.spaceKey.equals(this._spaceKey), void 0, {
|
|
611
|
+
"~LogMeta": "~LogMeta",
|
|
612
|
+
F: __dxlog_file$5,
|
|
613
|
+
L: 48,
|
|
614
|
+
S: this,
|
|
615
|
+
A: ["assertion.spaceKey.equals(this._spaceKey)", ""]
|
|
616
|
+
});
|
|
617
|
+
const info = {
|
|
618
|
+
key: credential.subject.id,
|
|
619
|
+
credential,
|
|
620
|
+
assertion,
|
|
621
|
+
parent: fromFeed
|
|
622
|
+
};
|
|
623
|
+
this._feeds.set(credential.subject.id, info);
|
|
624
|
+
await this.onFeedAdmitted.callIfSet(info);
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
//#endregion
|
|
628
|
+
//#region src/state-machine/invitation-state-machine.ts
|
|
629
|
+
/**
|
|
630
|
+
* Tracks the feed tree for a space.
|
|
631
|
+
* Provides a list of admitted feeds.
|
|
632
|
+
*/
|
|
633
|
+
var InvitationStateMachine = class {
|
|
634
|
+
_invitations = new ComplexMap(PublicKey.hash);
|
|
635
|
+
_redeemedInvitationCredentialIds = new ComplexSet(PublicKey.hash);
|
|
636
|
+
_cancelledInvitationCredentialIds = new ComplexSet(PublicKey.hash);
|
|
637
|
+
onDelegatedInvitation = new Callback();
|
|
638
|
+
onDelegatedInvitationRemoved = new Callback();
|
|
639
|
+
get invitations() {
|
|
640
|
+
return this._invitations;
|
|
641
|
+
}
|
|
642
|
+
async process(credential) {
|
|
643
|
+
if (credential.id == null) return;
|
|
644
|
+
const assertion = getCredentialAssertion(credential);
|
|
645
|
+
switch (assertion["@type"]) {
|
|
646
|
+
case "dxos.halo.invitations.CancelDelegatedInvitation": {
|
|
647
|
+
this._cancelledInvitationCredentialIds.add(assertion.credentialId);
|
|
648
|
+
const existingInvitation = this._invitations.get(assertion.credentialId);
|
|
649
|
+
if (existingInvitation != null) {
|
|
650
|
+
this._invitations.delete(assertion.credentialId);
|
|
651
|
+
await this.onDelegatedInvitationRemoved.callIfSet({
|
|
652
|
+
credentialId: assertion.credentialId,
|
|
653
|
+
invitation: existingInvitation
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
break;
|
|
657
|
+
}
|
|
658
|
+
case "dxos.halo.invitations.DelegateSpaceInvitation":
|
|
659
|
+
if (credential.id) {
|
|
660
|
+
const isExpired = assertion.expiresOn && assertion.expiresOn.getTime() < Date.now();
|
|
661
|
+
const wasUsed = this._redeemedInvitationCredentialIds.has(credential.id) && !assertion.multiUse;
|
|
662
|
+
const wasCancelled = this._cancelledInvitationCredentialIds.has(credential.id);
|
|
663
|
+
if (isExpired || wasCancelled || wasUsed) return;
|
|
664
|
+
const invitation = { ...assertion };
|
|
665
|
+
this._invitations.set(credential.id, invitation);
|
|
666
|
+
await this.onDelegatedInvitation.callIfSet({
|
|
667
|
+
credentialId: credential.id,
|
|
668
|
+
invitation
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
break;
|
|
672
|
+
case "dxos.halo.credentials.SpaceMember":
|
|
673
|
+
if (assertion.invitationCredentialId != null) {
|
|
674
|
+
this._redeemedInvitationCredentialIds.add(assertion.invitationCredentialId);
|
|
675
|
+
const existingInvitation = this._invitations.get(assertion.invitationCredentialId);
|
|
676
|
+
if (existingInvitation != null && !existingInvitation.multiUse) {
|
|
677
|
+
this._invitations.delete(assertion.invitationCredentialId);
|
|
678
|
+
await this.onDelegatedInvitationRemoved.callIfSet({
|
|
679
|
+
credentialId: assertion.invitationCredentialId,
|
|
680
|
+
invitation: existingInvitation
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
break;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
};
|
|
688
|
+
//#endregion
|
|
689
|
+
//#region src/graph/credential-graph.ts
|
|
690
|
+
var __dxlog_file$4 = "/__w/dxos/dxos/packages/core/halo/credentials/src/graph/credential-graph.ts";
|
|
691
|
+
var CredentialGraph = class {
|
|
692
|
+
_stateHandler;
|
|
693
|
+
/**
|
|
694
|
+
* Local ids are used during traversals.
|
|
695
|
+
*/
|
|
696
|
+
_vertexIdGenerator = 1;
|
|
697
|
+
/**
|
|
698
|
+
* All credentials without parent references are connected to the root.
|
|
699
|
+
*/
|
|
700
|
+
_root = {
|
|
701
|
+
id: -1,
|
|
702
|
+
parents: [],
|
|
703
|
+
children: []
|
|
704
|
+
};
|
|
705
|
+
/**
|
|
706
|
+
* A credential which is not a parent of any other credential has the sentinel as a child.
|
|
707
|
+
* Sentinel is a virtual merge-point of all credentials.
|
|
708
|
+
*/
|
|
709
|
+
_sentinel = {
|
|
710
|
+
id: -2,
|
|
711
|
+
parents: [],
|
|
712
|
+
children: []
|
|
713
|
+
};
|
|
714
|
+
/**
|
|
715
|
+
* Vertex references are used for fast credential inserts into the graph.
|
|
716
|
+
*/
|
|
717
|
+
_vertexByCredentialId = new ComplexMap(PublicKey.hash);
|
|
718
|
+
/**
|
|
719
|
+
* The current state of the graph.
|
|
720
|
+
*/
|
|
721
|
+
_subjectToVertex = new ComplexMap(PublicKey.hash);
|
|
722
|
+
_subjectToState = new ComplexMap(PublicKey.hash);
|
|
723
|
+
onSubjectStateChanged = new Callback();
|
|
724
|
+
constructor(_stateHandler) {
|
|
725
|
+
this._stateHandler = _stateHandler;
|
|
726
|
+
}
|
|
727
|
+
getSubjectState(subjectId) {
|
|
728
|
+
return this._subjectToState.get(subjectId);
|
|
729
|
+
}
|
|
730
|
+
getState() {
|
|
731
|
+
return this._subjectToState;
|
|
732
|
+
}
|
|
733
|
+
getLeafIds() {
|
|
734
|
+
return this._sentinel.parents.map((v) => v.credential.id);
|
|
735
|
+
}
|
|
736
|
+
getGlobalStateScope() {
|
|
737
|
+
return { state: this._subjectToVertex };
|
|
738
|
+
}
|
|
739
|
+
addVertex(credential, assertion) {
|
|
740
|
+
const newVertex = {
|
|
741
|
+
id: this._vertexIdGenerator++,
|
|
742
|
+
credential,
|
|
743
|
+
assertion,
|
|
744
|
+
parents: [],
|
|
745
|
+
children: []
|
|
746
|
+
};
|
|
747
|
+
this._vertexByCredentialId.set(credential.id, newVertex);
|
|
748
|
+
const parentIds = credential.parentCredentialIds ?? [];
|
|
749
|
+
if (parentIds.length === 0) {
|
|
750
|
+
this._root.children.push(newVertex);
|
|
751
|
+
newVertex.parents.push(this._root);
|
|
752
|
+
} else for (const parentId of parentIds) {
|
|
753
|
+
const parentVertex = this._vertexByCredentialId.get(parentId);
|
|
754
|
+
if (parentVertex == null) {
|
|
755
|
+
log.error("credential skipped because of the unknown parent", {
|
|
756
|
+
credential,
|
|
757
|
+
parentId
|
|
758
|
+
}, {
|
|
759
|
+
"~LogMeta": "~LogMeta",
|
|
760
|
+
F: __dxlog_file$4,
|
|
761
|
+
L: 72,
|
|
762
|
+
S: this
|
|
763
|
+
});
|
|
764
|
+
continue;
|
|
765
|
+
}
|
|
766
|
+
parentVertex.children.push(newVertex);
|
|
767
|
+
newVertex.parents.push(parentVertex);
|
|
768
|
+
this._removeSentinelConnection(parentVertex);
|
|
769
|
+
}
|
|
770
|
+
newVertex.children.push(this._sentinel);
|
|
771
|
+
this._sentinel.parents.push(newVertex);
|
|
772
|
+
return this._onVertexInserted(newVertex);
|
|
773
|
+
}
|
|
774
|
+
_removeSentinelConnection(vertex) {
|
|
775
|
+
const sentinelIdx = vertex.children.indexOf(this._sentinel);
|
|
776
|
+
if (sentinelIdx >= 0) {
|
|
777
|
+
vertex.children.splice(sentinelIdx, 1);
|
|
778
|
+
const vertexInSentinelIdx = this._sentinel.parents.indexOf(vertex);
|
|
779
|
+
invariant(vertexInSentinelIdx >= 0, void 0, {
|
|
780
|
+
"~LogMeta": "~LogMeta",
|
|
781
|
+
F: __dxlog_file$4,
|
|
782
|
+
L: 90,
|
|
783
|
+
S: this,
|
|
784
|
+
A: ["vertexInSentinelIdx >= 0", ""]
|
|
785
|
+
});
|
|
786
|
+
this._sentinel.parents.splice(vertexInSentinelIdx, 1);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
async _onVertexInserted(newVertex) {
|
|
790
|
+
const { credential, assertion } = newVertex;
|
|
791
|
+
invariant(credential, void 0, {
|
|
792
|
+
"~LogMeta": "~LogMeta",
|
|
793
|
+
F: __dxlog_file$4,
|
|
794
|
+
L: 97,
|
|
795
|
+
S: this,
|
|
796
|
+
A: ["credential", ""]
|
|
797
|
+
});
|
|
798
|
+
let changedSubjects = [];
|
|
799
|
+
if (this._sentinel.parents.length === 1) {
|
|
800
|
+
const subjectId = credential.subject.id;
|
|
801
|
+
if (this._stateHandler.isUpdateAllowed(this.getGlobalStateScope(), credential, assertion)) {
|
|
802
|
+
const newSubjectState = this._stateHandler.createState(credential, newVertex.assertion);
|
|
803
|
+
const prevSubjectState = this._subjectToState.get(subjectId);
|
|
804
|
+
this._subjectToState.set(subjectId, newSubjectState);
|
|
805
|
+
this._subjectToVertex.set(subjectId, newVertex);
|
|
806
|
+
if (this._stateHandler.hasStateChanged(newSubjectState, prevSubjectState)) changedSubjects.push(newSubjectState);
|
|
807
|
+
}
|
|
808
|
+
} else changedSubjects = this._recomputeState();
|
|
809
|
+
if (changedSubjects.length > 0) await this.onSubjectStateChanged.callIfSet(changedSubjects);
|
|
810
|
+
}
|
|
811
|
+
/**
|
|
812
|
+
* DFS the graph from root to sentinel pausing on merge points (nodes with multiple parents).
|
|
813
|
+
* Continue after all paths leading to a merge point converge by merging their states.
|
|
814
|
+
* In case of a concurrent update paths are replayed taking into account the state set
|
|
815
|
+
* by the winning branch.
|
|
816
|
+
*/
|
|
817
|
+
_recomputeState() {
|
|
818
|
+
const pendingPaths = /* @__PURE__ */ new Map();
|
|
819
|
+
const paths = [this._createRootPath()];
|
|
820
|
+
let lastPath = null;
|
|
821
|
+
while (lastPath == null) {
|
|
822
|
+
const path = paths.pop();
|
|
823
|
+
log("visit vertex", { id: path.head.id }, {
|
|
824
|
+
"~LogMeta": "~LogMeta",
|
|
825
|
+
F: __dxlog_file$4,
|
|
826
|
+
L: 132,
|
|
827
|
+
S: this
|
|
828
|
+
});
|
|
829
|
+
this._updatePathState(path);
|
|
830
|
+
const convergedPaths = this._handleMergePoint(paths, pendingPaths, path);
|
|
831
|
+
if (convergedPaths == null) {
|
|
832
|
+
log("waiting for other paths", void 0, {
|
|
833
|
+
"~LogMeta": "~LogMeta",
|
|
834
|
+
F: __dxlog_file$4,
|
|
835
|
+
L: 136,
|
|
836
|
+
S: this
|
|
837
|
+
});
|
|
838
|
+
continue;
|
|
839
|
+
}
|
|
840
|
+
const mergeResult = this._mergePaths(convergedPaths);
|
|
841
|
+
if (mergeResult.type === "replay_required") {
|
|
842
|
+
this._replayFailedPaths(paths, pendingPaths, mergeResult, convergedPaths);
|
|
843
|
+
continue;
|
|
844
|
+
}
|
|
845
|
+
const merged = mergeResult.path;
|
|
846
|
+
if (merged.head.children.length === 0) lastPath = merged;
|
|
847
|
+
else if (merged.head.children.length === 1) {
|
|
848
|
+
merged.head = merged.head.children[0];
|
|
849
|
+
paths.push(merged);
|
|
850
|
+
} else this._forkTraversal(paths, merged);
|
|
851
|
+
}
|
|
852
|
+
if (paths.length > 0) log.error("traversal finished while there were active paths", { paths: paths.map((p) => ({
|
|
853
|
+
path: toChosenPath(p),
|
|
854
|
+
head: p.head.id
|
|
855
|
+
})) }, {
|
|
856
|
+
"~LogMeta": "~LogMeta",
|
|
857
|
+
F: __dxlog_file$4,
|
|
858
|
+
L: 155,
|
|
859
|
+
S: this
|
|
860
|
+
});
|
|
861
|
+
return this._setCurrentState(lastPath);
|
|
862
|
+
}
|
|
863
|
+
_replayFailedPaths(paths, pendingPaths, mergeResult, convergedPaths) {
|
|
864
|
+
paths.push(...mergeResult.replay.map((path) => {
|
|
865
|
+
const stateOverrides = path.stateOverrides ?? new ComplexMap(PublicKey.hash);
|
|
866
|
+
mergeResult.stateOverrides.forEach((value, key) => stateOverrides.set(key, value));
|
|
867
|
+
return {
|
|
868
|
+
...mergeResult.from,
|
|
869
|
+
chosenPath: path.chosenPath,
|
|
870
|
+
stateOverrides
|
|
871
|
+
};
|
|
872
|
+
}));
|
|
873
|
+
log("replay paths", () => ({
|
|
874
|
+
count: paths.length,
|
|
875
|
+
paths: paths.map((path) => ({
|
|
876
|
+
from: mergeResult.from.head.id,
|
|
877
|
+
path: toChosenPath(path),
|
|
878
|
+
overrides: path?.stateOverrides?.mapValues((v) => this._stateHandler.toLogString(v.assertion))
|
|
879
|
+
}))
|
|
880
|
+
}), {
|
|
881
|
+
"~LogMeta": "~LogMeta",
|
|
882
|
+
F: __dxlog_file$4,
|
|
883
|
+
L: 175,
|
|
884
|
+
S: this
|
|
885
|
+
});
|
|
886
|
+
const clearedPending = convergedPaths.filter((l) => !mergeResult.replay.includes(l));
|
|
887
|
+
pendingPaths.set(convergedPaths[0].head.id, clearedPending);
|
|
888
|
+
}
|
|
889
|
+
_handleMergePoint(paths, pendingPaths, path) {
|
|
890
|
+
const pendingList = pendingPaths.get(path.head.id) ?? [];
|
|
891
|
+
pendingPaths.set(path.head.id, pendingList);
|
|
892
|
+
pendingList.push(path);
|
|
893
|
+
if (pendingList.length < path.head.parents.length) return null;
|
|
894
|
+
if (path.head.id === this._sentinel.id && paths.length > 0) {
|
|
895
|
+
log("waiting for all the active paths to converge on the sentinel", void 0, {
|
|
896
|
+
"~LogMeta": "~LogMeta",
|
|
897
|
+
F: __dxlog_file$4,
|
|
898
|
+
L: 199,
|
|
899
|
+
S: this
|
|
900
|
+
});
|
|
901
|
+
return null;
|
|
902
|
+
}
|
|
903
|
+
pendingPaths.delete(path.head.id);
|
|
904
|
+
return pendingList;
|
|
905
|
+
}
|
|
906
|
+
_updatePathState(path) {
|
|
907
|
+
const headCredential = path.head.credential;
|
|
908
|
+
if (headCredential == null) return;
|
|
909
|
+
const updatedSubject = headCredential.subject.id;
|
|
910
|
+
path.credentials.add(headCredential.id);
|
|
911
|
+
let isUpdateAllowed = this._stateHandler.isUpdateAllowed(path, headCredential, path.head.assertion);
|
|
912
|
+
if (!isUpdateAllowed && path.head.parents[0]?.id === this._root.id) {
|
|
913
|
+
const globalState = this.getGlobalStateScope();
|
|
914
|
+
isUpdateAllowed = this._stateHandler.isUpdateAllowed(globalState, headCredential, path.head.assertion);
|
|
915
|
+
}
|
|
916
|
+
if (isUpdateAllowed) {
|
|
917
|
+
path.forkChangedSubjects.add(updatedSubject);
|
|
918
|
+
path.forkIssuers.add(headCredential.issuer);
|
|
919
|
+
path.state.set(updatedSubject, path.head);
|
|
920
|
+
log("path state updated", () => ({
|
|
921
|
+
subject: updatedSubject,
|
|
922
|
+
newState: this._stateHandler.toLogString(path.head.assertion)
|
|
923
|
+
}), {
|
|
924
|
+
"~LogMeta": "~LogMeta",
|
|
925
|
+
F: __dxlog_file$4,
|
|
926
|
+
L: 223,
|
|
927
|
+
S: this
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
_forkTraversal(paths, path) {
|
|
932
|
+
const choices = path.chosenPath?.[path.head.id] ?? path.head.children;
|
|
933
|
+
for (const choice of choices) {
|
|
934
|
+
log("edge traversal", {
|
|
935
|
+
from: path.head.id,
|
|
936
|
+
to: choice.id
|
|
937
|
+
}, {
|
|
938
|
+
"~LogMeta": "~LogMeta",
|
|
939
|
+
F: __dxlog_file$4,
|
|
940
|
+
L: 234,
|
|
941
|
+
S: this
|
|
942
|
+
});
|
|
943
|
+
const fork = {
|
|
944
|
+
forkPoint: path,
|
|
945
|
+
chosenPath: {
|
|
946
|
+
...path.chosenPath,
|
|
947
|
+
[path.head.id]: [choice]
|
|
948
|
+
},
|
|
949
|
+
head: choice,
|
|
950
|
+
credentials: new ComplexSet(PublicKey.hash, path.credentials),
|
|
951
|
+
state: new ComplexMap(PublicKey.hash, [...path.state.entries()]),
|
|
952
|
+
forkIssuers: new ComplexSet(PublicKey.hash),
|
|
953
|
+
forkChangedSubjects: new ComplexSet(PublicKey.hash),
|
|
954
|
+
stateOverrides: path.stateOverrides
|
|
955
|
+
};
|
|
956
|
+
paths.push(fork);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* Updates the current graph state.
|
|
961
|
+
* @returns changed states.
|
|
962
|
+
*/
|
|
963
|
+
_setCurrentState(path) {
|
|
964
|
+
const changedSubjects = [];
|
|
965
|
+
const newStateMap = new ComplexMap(PublicKey.hash);
|
|
966
|
+
const newVertexMap = new ComplexMap(PublicKey.hash);
|
|
967
|
+
for (const [subjectKey, subjectVertex] of path.state.entries()) {
|
|
968
|
+
const newState = this._stateHandler.createState(subjectVertex.credential, subjectVertex.assertion);
|
|
969
|
+
const prevState = this._subjectToState.get(subjectKey);
|
|
970
|
+
newStateMap.set(subjectKey, newState);
|
|
971
|
+
newVertexMap.set(subjectKey, subjectVertex);
|
|
972
|
+
if (this._stateHandler.hasStateChanged(newState, prevState)) changedSubjects.push(newState);
|
|
973
|
+
}
|
|
974
|
+
this._subjectToState = newStateMap;
|
|
975
|
+
this._subjectToVertex = newVertexMap;
|
|
976
|
+
return changedSubjects;
|
|
977
|
+
}
|
|
978
|
+
_leastCommonAncestor(paths) {
|
|
979
|
+
const uniqueForkPoints = paths.reduce((acc, path) => {
|
|
980
|
+
let it = path.forkPoint;
|
|
981
|
+
while (it) {
|
|
982
|
+
acc.set(it.head.id, it);
|
|
983
|
+
it = it.forkPoint;
|
|
984
|
+
}
|
|
985
|
+
return acc;
|
|
986
|
+
}, /* @__PURE__ */ new Map());
|
|
987
|
+
let maxId = this._root.id;
|
|
988
|
+
let maxState = null;
|
|
989
|
+
for (const [id, state] of uniqueForkPoints.entries()) {
|
|
990
|
+
const headCredential = state.head.credential;
|
|
991
|
+
if (headCredential != null) {
|
|
992
|
+
if (paths.every((p) => p.credentials.has(headCredential.id)) && id > maxId) {
|
|
993
|
+
maxId = id;
|
|
994
|
+
maxState = state;
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
return maxState ?? this._createRootPath();
|
|
999
|
+
}
|
|
1000
|
+
/**
|
|
1001
|
+
* We might be merging paths where some of them had fork points after the initial forking.
|
|
1002
|
+
* We need all the paths to point to the least common fork point and contain all the changes
|
|
1003
|
+
* that happened after it.
|
|
1004
|
+
*/
|
|
1005
|
+
_moveUpToForkPoint(forkPoint, path) {
|
|
1006
|
+
if (path.chosenPath[forkPoint.head.id] == null || path.forkPoint == null) return path;
|
|
1007
|
+
if (forkPoint.head.id === path.forkPoint?.head.id) return path;
|
|
1008
|
+
let it = path.forkPoint;
|
|
1009
|
+
while (it.head.id !== forkPoint.head.id) {
|
|
1010
|
+
it.forkIssuers.forEach((iss) => path.forkIssuers.add(iss));
|
|
1011
|
+
it.forkChangedSubjects.forEach((m) => path.forkChangedSubjects.add(m));
|
|
1012
|
+
it = it.forkPoint;
|
|
1013
|
+
path.forkPoint = it;
|
|
1014
|
+
}
|
|
1015
|
+
return path;
|
|
1016
|
+
}
|
|
1017
|
+
_mergePaths(convergedPaths) {
|
|
1018
|
+
invariant(convergedPaths.length >= 1, void 0, {
|
|
1019
|
+
"~LogMeta": "~LogMeta",
|
|
1020
|
+
F: __dxlog_file$4,
|
|
1021
|
+
L: 324,
|
|
1022
|
+
S: this,
|
|
1023
|
+
A: ["convergedPaths.length >= 1", ""]
|
|
1024
|
+
});
|
|
1025
|
+
if (convergedPaths.length === 1) return {
|
|
1026
|
+
type: "merged",
|
|
1027
|
+
path: convergedPaths[0]
|
|
1028
|
+
};
|
|
1029
|
+
const forkPoint = this._leastCommonAncestor(convergedPaths);
|
|
1030
|
+
log("merging paths", () => ({
|
|
1031
|
+
forkPointId: forkPoint.head.id,
|
|
1032
|
+
pathCount: convergedPaths.length,
|
|
1033
|
+
forkPoints: convergedPaths.map((fp) => fp.forkPoint?.head.id)
|
|
1034
|
+
}), {
|
|
1035
|
+
"~LogMeta": "~LogMeta",
|
|
1036
|
+
F: __dxlog_file$4,
|
|
1037
|
+
L: 329,
|
|
1038
|
+
S: this
|
|
1039
|
+
});
|
|
1040
|
+
const paths = convergedPaths.map((p) => this._moveUpToForkPoint(forkPoint, p));
|
|
1041
|
+
invariant(forkPoint, void 0, {
|
|
1042
|
+
"~LogMeta": "~LogMeta",
|
|
1043
|
+
F: __dxlog_file$4,
|
|
1044
|
+
L: 335,
|
|
1045
|
+
S: this,
|
|
1046
|
+
A: ["forkPoint", ""]
|
|
1047
|
+
});
|
|
1048
|
+
const result = {
|
|
1049
|
+
forkPoint: forkPoint.forkPoint,
|
|
1050
|
+
chosenPath: {
|
|
1051
|
+
...forkPoint.chosenPath,
|
|
1052
|
+
[forkPoint.head.id]: []
|
|
1053
|
+
},
|
|
1054
|
+
stateOverrides: forkPoint.stateOverrides,
|
|
1055
|
+
credentials: new ComplexSet(PublicKey.hash, forkPoint.credentials),
|
|
1056
|
+
forkIssuers: new ComplexSet(PublicKey.hash, forkPoint.forkIssuers),
|
|
1057
|
+
forkChangedSubjects: new ComplexSet(PublicKey.hash, forkPoint.forkChangedSubjects),
|
|
1058
|
+
state: forkPoint.state.mapValues((v) => v),
|
|
1059
|
+
head: paths[0].head
|
|
1060
|
+
};
|
|
1061
|
+
const subjectToBranch = new ComplexMap(PublicKey.hash);
|
|
1062
|
+
for (const path of paths) {
|
|
1063
|
+
log("processing a path", () => ({
|
|
1064
|
+
choices: toChosenPath(path),
|
|
1065
|
+
modified: path.forkChangedSubjects,
|
|
1066
|
+
forkIssuers: path.forkIssuers,
|
|
1067
|
+
state: path.state.mapValues((v) => this._stateHandler.toLogString(v.assertion))
|
|
1068
|
+
}), {
|
|
1069
|
+
"~LogMeta": "~LogMeta",
|
|
1070
|
+
F: __dxlog_file$4,
|
|
1071
|
+
L: 348,
|
|
1072
|
+
S: this
|
|
1073
|
+
});
|
|
1074
|
+
path.forkIssuers.forEach((iss) => result.forkIssuers.add(iss));
|
|
1075
|
+
path.credentials.forEach((cred) => result.credentials.add(cred));
|
|
1076
|
+
result.chosenPath[forkPoint.head.id].push(...path.chosenPath[forkPoint.head.id] ?? []);
|
|
1077
|
+
for (const modifiedSubject of path.forkChangedSubjects) {
|
|
1078
|
+
const existingBranch = subjectToBranch.get(modifiedSubject);
|
|
1079
|
+
if (existingBranch == null || this._shouldOverrideCredential(existingBranch, path, modifiedSubject)) subjectToBranch.set(modifiedSubject, path);
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
const replayPaths = /* @__PURE__ */ new Set();
|
|
1083
|
+
const addReplayPath = replayPaths.add.bind(replayPaths);
|
|
1084
|
+
for (const [subject, branch] of subjectToBranch.entries()) {
|
|
1085
|
+
result.forkChangedSubjects.add(subject);
|
|
1086
|
+
const vertex = branch.state.get(subject);
|
|
1087
|
+
result.state.set(subject, vertex);
|
|
1088
|
+
log("set subject state", () => ({
|
|
1089
|
+
subject,
|
|
1090
|
+
state: this._stateHandler.toLogString(vertex.assertion)
|
|
1091
|
+
}), {
|
|
1092
|
+
"~LogMeta": "~LogMeta",
|
|
1093
|
+
F: __dxlog_file$4,
|
|
1094
|
+
L: 370,
|
|
1095
|
+
S: this
|
|
1096
|
+
});
|
|
1097
|
+
const otherPaths = paths.filter((p) => p !== branch);
|
|
1098
|
+
this._stateHandler.getConflictingPaths(otherPaths, vertex).forEach(addReplayPath);
|
|
1099
|
+
}
|
|
1100
|
+
if (replayPaths.size > 0) return {
|
|
1101
|
+
type: "replay_required",
|
|
1102
|
+
replay: [...replayPaths.values()],
|
|
1103
|
+
from: forkPoint,
|
|
1104
|
+
stateOverrides: subjectToBranch.mapValues((v, key) => v.state.get(key))
|
|
1105
|
+
};
|
|
1106
|
+
return {
|
|
1107
|
+
type: "merged",
|
|
1108
|
+
path: result
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
1111
|
+
/**
|
|
1112
|
+
* A candidate credential is preferred over the existing credential if:
|
|
1113
|
+
* 1. It is the merge-point, because it's the last credential that was issued in awareness of all
|
|
1114
|
+
* the previously existing ones.
|
|
1115
|
+
* 2. A path where candidate was set contains existing credential in it, which means that the candidate
|
|
1116
|
+
* was issued after the existing credential by a legitimate issuer.
|
|
1117
|
+
* 3. A state-specific logic (_stateHandler) is able to justify using the candidate credential.
|
|
1118
|
+
* 4. The path where candidate was set has more issuers than the existing path (longer branch).
|
|
1119
|
+
* 5. The issuance time of the candidate is after the issuance time of the existing credential (LWW).
|
|
1120
|
+
*/
|
|
1121
|
+
_shouldOverrideCredential(existing, candidate, modifiedSubject) {
|
|
1122
|
+
const candidateVertex = candidate.state.get(modifiedSubject);
|
|
1123
|
+
const currentVertex = existing.state.get(modifiedSubject);
|
|
1124
|
+
if (candidateVertex.id === currentVertex.id) return false;
|
|
1125
|
+
const mergePointId = existing.head.id;
|
|
1126
|
+
if (candidateVertex.id === mergePointId || currentVertex.id === mergePointId) {
|
|
1127
|
+
log("merge point chosen to break the tie", { mergePointId: existing.head.id }, {
|
|
1128
|
+
"~LogMeta": "~LogMeta",
|
|
1129
|
+
F: __dxlog_file$4,
|
|
1130
|
+
L: 408,
|
|
1131
|
+
S: this
|
|
1132
|
+
});
|
|
1133
|
+
return mergePointId === candidateVertex.id;
|
|
1134
|
+
}
|
|
1135
|
+
const candidateCredential = candidateVertex.credential;
|
|
1136
|
+
const currentCredential = currentVertex.credential;
|
|
1137
|
+
if (existing.credentials.has(candidateCredential.id) !== candidate.credentials.has(currentCredential.id)) {
|
|
1138
|
+
log("one of the credentials was overridden in another branch", {
|
|
1139
|
+
current: currentVertex.id,
|
|
1140
|
+
candidate: candidateVertex.id
|
|
1141
|
+
}, {
|
|
1142
|
+
"~LogMeta": "~LogMeta",
|
|
1143
|
+
F: __dxlog_file$4,
|
|
1144
|
+
L: 415,
|
|
1145
|
+
S: this
|
|
1146
|
+
});
|
|
1147
|
+
return candidate.credentials.has(currentCredential.id);
|
|
1148
|
+
}
|
|
1149
|
+
const winningCredential = this._stateHandler.tryPickWinningUpdate(existing, currentCredential, candidate, candidateCredential);
|
|
1150
|
+
if (winningCredential != null) return winningCredential === candidateCredential;
|
|
1151
|
+
if (candidate.forkIssuers.size !== existing.forkIssuers.size) {
|
|
1152
|
+
log("longer issuers branch used to break the tie", { issuerCount: [existing.forkIssuers.size, candidate.forkIssuers.size] }, {
|
|
1153
|
+
"~LogMeta": "~LogMeta",
|
|
1154
|
+
F: __dxlog_file$4,
|
|
1155
|
+
L: 432,
|
|
1156
|
+
S: this
|
|
1157
|
+
});
|
|
1158
|
+
return candidate.forkIssuers.size > existing.forkIssuers.size;
|
|
1159
|
+
}
|
|
1160
|
+
log("issuance date used to break the tie", void 0, {
|
|
1161
|
+
"~LogMeta": "~LogMeta",
|
|
1162
|
+
F: __dxlog_file$4,
|
|
1163
|
+
L: 437,
|
|
1164
|
+
S: this
|
|
1165
|
+
});
|
|
1166
|
+
return candidateCredential.issuanceDate.getTime() > currentCredential.issuanceDate.getTime();
|
|
1167
|
+
}
|
|
1168
|
+
_createRootPath() {
|
|
1169
|
+
return {
|
|
1170
|
+
head: this._root,
|
|
1171
|
+
chosenPath: {},
|
|
1172
|
+
forkIssuers: new ComplexSet(PublicKey.hash),
|
|
1173
|
+
forkChangedSubjects: new ComplexSet(PublicKey.hash),
|
|
1174
|
+
state: new ComplexMap(PublicKey.hash),
|
|
1175
|
+
credentials: new ComplexSet(PublicKey.hash)
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
};
|
|
1179
|
+
var toChosenPath = (path) => {
|
|
1180
|
+
return Object.fromEntries(Object.entries(path.chosenPath).map(([k, vs]) => [k, vs.map((v) => v.id)]));
|
|
1181
|
+
};
|
|
1182
|
+
//#endregion
|
|
1183
|
+
//#region src/state-machine/member-state-machine.ts
|
|
1184
|
+
var __dxlog_file$3 = "/__w/dxos/dxos/packages/core/halo/credentials/src/state-machine/member-state-machine.ts";
|
|
1185
|
+
/**
|
|
1186
|
+
* Tracks the list of members (with roles) for the space.
|
|
1187
|
+
* Provides a list of admitted feeds.
|
|
1188
|
+
*/
|
|
1189
|
+
var MemberStateMachine = class {
|
|
1190
|
+
_spaceKey;
|
|
1191
|
+
_ownerKey;
|
|
1192
|
+
_memberProfiles = new ComplexMap(PublicKey.hash);
|
|
1193
|
+
_hashgraph = new CredentialGraph(this);
|
|
1194
|
+
onMemberRoleChanged = this._hashgraph.onSubjectStateChanged;
|
|
1195
|
+
constructor(_spaceKey) {
|
|
1196
|
+
this._spaceKey = _spaceKey;
|
|
1197
|
+
}
|
|
1198
|
+
get creator() {
|
|
1199
|
+
return this._ownerKey && this._hashgraph.getSubjectState(this._ownerKey);
|
|
1200
|
+
}
|
|
1201
|
+
get members() {
|
|
1202
|
+
return this._hashgraph.getState();
|
|
1203
|
+
}
|
|
1204
|
+
get membershipChainHeads() {
|
|
1205
|
+
return this._hashgraph.getLeafIds();
|
|
1206
|
+
}
|
|
1207
|
+
getRole(member) {
|
|
1208
|
+
return this._getRole(this._hashgraph.getGlobalStateScope(), member);
|
|
1209
|
+
}
|
|
1210
|
+
/**
|
|
1211
|
+
* Processes the SpaceMember credential.
|
|
1212
|
+
* Assumes the credential is already pre-verified and the issuer has been authorized to issue credentials of this type.
|
|
1213
|
+
*/
|
|
1214
|
+
async process(credential) {
|
|
1215
|
+
const assertion = getCredentialAssertion(credential);
|
|
1216
|
+
switch (assertion["@type"]) {
|
|
1217
|
+
case "dxos.halo.credentials.SpaceMember":
|
|
1218
|
+
invariant(assertion.spaceKey.equals(this._spaceKey), void 0, {
|
|
1219
|
+
"~LogMeta": "~LogMeta",
|
|
1220
|
+
F: __dxlog_file$3,
|
|
1221
|
+
L: 66,
|
|
1222
|
+
S: this,
|
|
1223
|
+
A: ["assertion.spaceKey.equals(this._spaceKey)", ""]
|
|
1224
|
+
});
|
|
1225
|
+
if (this._ownerKey == null && credential.issuer === this._spaceKey) this._ownerKey = credential.subject.id;
|
|
1226
|
+
if (assertion.profile != null) this._memberProfiles.set(credential.subject.id, assertion.profile);
|
|
1227
|
+
await this._hashgraph.addVertex(credential, assertion);
|
|
1228
|
+
break;
|
|
1229
|
+
case "dxos.halo.credentials.MemberProfile": {
|
|
1230
|
+
const member = this._hashgraph.getSubjectState(credential.subject.id);
|
|
1231
|
+
if (member) member.profile = assertion.profile;
|
|
1232
|
+
else log.warn("Member not found", { id: credential.subject.id }, {
|
|
1233
|
+
"~LogMeta": "~LogMeta",
|
|
1234
|
+
F: __dxlog_file$3,
|
|
1235
|
+
L: 81,
|
|
1236
|
+
S: this
|
|
1237
|
+
});
|
|
1238
|
+
this._memberProfiles.set(credential.subject.id, assertion.profile);
|
|
1239
|
+
break;
|
|
1240
|
+
}
|
|
1241
|
+
default: throw new Error("Invalid assertion type");
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
createState(credential, assertion) {
|
|
1245
|
+
const memberKey = credential.subject.id;
|
|
1246
|
+
return {
|
|
1247
|
+
key: memberKey,
|
|
1248
|
+
role: assertion.role,
|
|
1249
|
+
credential,
|
|
1250
|
+
assertion,
|
|
1251
|
+
profile: this._memberProfiles.get(memberKey)
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
1254
|
+
isUpdateAllowed(scope, credential, assertion) {
|
|
1255
|
+
if (assertion.role === SpaceMember.Role.OWNER) return credential.issuer.equals(this._spaceKey);
|
|
1256
|
+
const issuer = credential.issuer;
|
|
1257
|
+
if (issuer.equals(credential.subject.id)) return false;
|
|
1258
|
+
if (issuer.equals(assertion.spaceKey)) return true;
|
|
1259
|
+
const issuerRole = this._getRole(scope, issuer);
|
|
1260
|
+
return issuerRole === SpaceMember.Role.ADMIN || issuerRole === SpaceMember.Role.OWNER;
|
|
1261
|
+
}
|
|
1262
|
+
getConflictingPaths(paths, update) {
|
|
1263
|
+
if (update.assertion.role !== SpaceMember.Role.REMOVED && update.assertion.role !== SpaceMember.Role.EDITOR) return [];
|
|
1264
|
+
const memberId = update.credential.subject.id;
|
|
1265
|
+
return paths.filter((p) => p.forkIssuers.has(memberId));
|
|
1266
|
+
}
|
|
1267
|
+
tryPickWinningUpdate(scope1, update1, scope2, update2) {
|
|
1268
|
+
const path1IssuerRole = this._getRole(scope1, update1.issuer);
|
|
1269
|
+
if (this._getRole(scope2, update2.issuer) === SpaceMember.Role.OWNER !== (path1IssuerRole === SpaceMember.Role.OWNER)) {
|
|
1270
|
+
log("owner decision used to break the tie", void 0, {
|
|
1271
|
+
"~LogMeta": "~LogMeta",
|
|
1272
|
+
F: __dxlog_file$3,
|
|
1273
|
+
L: 139,
|
|
1274
|
+
S: this
|
|
1275
|
+
});
|
|
1276
|
+
return path1IssuerRole === SpaceMember.Role.OWNER ? update1 : update2;
|
|
1277
|
+
}
|
|
1278
|
+
return null;
|
|
1279
|
+
}
|
|
1280
|
+
toLogString(assertion) {
|
|
1281
|
+
const role = assertion?.role ?? SpaceMember.Role.REMOVED;
|
|
1282
|
+
return Object.entries(SpaceMember.Role).find(([_, value]) => value === role)[0];
|
|
1283
|
+
}
|
|
1284
|
+
hasStateChanged(s1, s2) {
|
|
1285
|
+
return s1?.role !== s2?.role;
|
|
1286
|
+
}
|
|
1287
|
+
_getRole(scope, memberId) {
|
|
1288
|
+
if (this._ownerKey?.equals(memberId)) return SpaceMember.Role.OWNER;
|
|
1289
|
+
const realRole = scope.state.get(memberId)?.assertion?.role ?? SpaceMember.Role.REMOVED;
|
|
1290
|
+
if (scope.stateOverrides != null) {
|
|
1291
|
+
const override = scope.stateOverrides.get(memberId);
|
|
1292
|
+
if (override != null) {
|
|
1293
|
+
log("member role overridden in path", () => ({
|
|
1294
|
+
headId: scope.head?.id,
|
|
1295
|
+
roleOverride: this.toLogString(override.assertion),
|
|
1296
|
+
realRole: this.toLogString(scope.state.get(memberId)?.assertion)
|
|
1297
|
+
}), {
|
|
1298
|
+
"~LogMeta": "~LogMeta",
|
|
1299
|
+
F: __dxlog_file$3,
|
|
1300
|
+
L: 162,
|
|
1301
|
+
S: this
|
|
1302
|
+
});
|
|
1303
|
+
return override.assertion.role;
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
return realRole;
|
|
1307
|
+
}
|
|
1308
|
+
};
|
|
1309
|
+
//#endregion
|
|
1310
|
+
//#region \0@oxc-project+runtime@0.139.0/helpers/esm/decorate.js
|
|
1311
|
+
function __decorate(decorators, target, key, desc) {
|
|
1312
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1313
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1314
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
1315
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1316
|
+
}
|
|
1317
|
+
//#endregion
|
|
1318
|
+
//#region src/state-machine/space-state-machine.ts
|
|
1319
|
+
var __dxlog_file$2 = "/__w/dxos/dxos/packages/core/halo/credentials/src/state-machine/space-state-machine.ts";
|
|
1320
|
+
/**
|
|
1321
|
+
* Validates and processes credentials for a single space.
|
|
1322
|
+
* Keeps a list of members and feeds.
|
|
1323
|
+
* Keeps and in-memory index of credentials and allows to query them.
|
|
1324
|
+
*/
|
|
1325
|
+
var SpaceStateMachine = class {
|
|
1326
|
+
_spaceKey;
|
|
1327
|
+
_members;
|
|
1328
|
+
_feeds;
|
|
1329
|
+
_invitations = new InvitationStateMachine();
|
|
1330
|
+
_credentials = [];
|
|
1331
|
+
_credentialsById = new ComplexMap(PublicKey.hash);
|
|
1332
|
+
_processedCredentials = new ComplexSet(PublicKey.hash);
|
|
1333
|
+
_genesisCredential;
|
|
1334
|
+
_tags = [];
|
|
1335
|
+
_membershipPolicy = MembershipPolicy.INVITE;
|
|
1336
|
+
_credentialProcessors = [];
|
|
1337
|
+
onCredentialProcessed = new Callback();
|
|
1338
|
+
onMemberRoleChanged;
|
|
1339
|
+
onFeedAdmitted;
|
|
1340
|
+
onDelegatedInvitation = this._invitations.onDelegatedInvitation;
|
|
1341
|
+
onDelegatedInvitationRemoved = this._invitations.onDelegatedInvitationRemoved;
|
|
1342
|
+
constructor(_spaceKey) {
|
|
1343
|
+
this._spaceKey = _spaceKey;
|
|
1344
|
+
this._members = new MemberStateMachine(this._spaceKey);
|
|
1345
|
+
this._feeds = new FeedStateMachine(this._spaceKey);
|
|
1346
|
+
this.onMemberRoleChanged = this._members.onMemberRoleChanged;
|
|
1347
|
+
this.onFeedAdmitted = this._feeds.onFeedAdmitted;
|
|
1348
|
+
}
|
|
1349
|
+
get creator() {
|
|
1350
|
+
return this._members.creator;
|
|
1351
|
+
}
|
|
1352
|
+
get members() {
|
|
1353
|
+
return this._members.members;
|
|
1354
|
+
}
|
|
1355
|
+
get membershipChainHeads() {
|
|
1356
|
+
return this._members.membershipChainHeads;
|
|
1357
|
+
}
|
|
1358
|
+
get feeds() {
|
|
1359
|
+
return this._feeds.feeds;
|
|
1360
|
+
}
|
|
1361
|
+
get credentials() {
|
|
1362
|
+
return this._credentials.map((entry) => entry.credential);
|
|
1363
|
+
}
|
|
1364
|
+
get credentialEntries() {
|
|
1365
|
+
return this._credentials;
|
|
1366
|
+
}
|
|
1367
|
+
get genesisCredential() {
|
|
1368
|
+
return this._genesisCredential;
|
|
1369
|
+
}
|
|
1370
|
+
get tags() {
|
|
1371
|
+
return this._tags;
|
|
1372
|
+
}
|
|
1373
|
+
get membershipPolicy() {
|
|
1374
|
+
return this._membershipPolicy;
|
|
1375
|
+
}
|
|
1376
|
+
get invitations() {
|
|
1377
|
+
return this._invitations.invitations;
|
|
1378
|
+
}
|
|
1379
|
+
async addCredentialProcessor(processor) {
|
|
1380
|
+
if (this._credentialProcessors.find((p) => p.processor === processor)) throw new Error("Credential processor already added.");
|
|
1381
|
+
const consumer = new CredentialConsumer(processor, async () => {
|
|
1382
|
+
for (const credential of this.credentials) await consumer._process(credential);
|
|
1383
|
+
consumer._isReadyForLiveCredentials = true;
|
|
1384
|
+
}, async () => {
|
|
1385
|
+
this._credentialProcessors = this._credentialProcessors.filter((p) => p !== consumer);
|
|
1386
|
+
});
|
|
1387
|
+
this._credentialProcessors.push(consumer);
|
|
1388
|
+
await consumer.open();
|
|
1389
|
+
}
|
|
1390
|
+
async removeCredentialProcessor(processor) {
|
|
1391
|
+
await this._credentialProcessors.find((p) => p.processor === processor)?.close();
|
|
1392
|
+
}
|
|
1393
|
+
getCredentialsOfType(type) {
|
|
1394
|
+
return this.credentials.filter((credential) => getCredentialAssertion(credential)["@type"] === type);
|
|
1395
|
+
}
|
|
1396
|
+
/**
|
|
1397
|
+
* @param credential Message to process.
|
|
1398
|
+
* @param fromFeed Key of the feed where this credential is recorded.
|
|
1399
|
+
*/
|
|
1400
|
+
async process(credential, { sourceFeed, skipVerification }) {
|
|
1401
|
+
if (credential.id) {
|
|
1402
|
+
if (this._processedCredentials.has(credential.id)) return true;
|
|
1403
|
+
this._processedCredentials.add(credential.id);
|
|
1404
|
+
}
|
|
1405
|
+
if (!skipVerification) {
|
|
1406
|
+
const result = await verifyCredential(credential);
|
|
1407
|
+
if (result.kind !== "pass") {
|
|
1408
|
+
log.warn(`Invalid credential: ${result.errors.join(", ")}`, void 0, {
|
|
1409
|
+
"~LogMeta": "~LogMeta",
|
|
1410
|
+
F: __dxlog_file$2,
|
|
1411
|
+
L: 173,
|
|
1412
|
+
S: this
|
|
1413
|
+
});
|
|
1414
|
+
return false;
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
const assertion = getCredentialAssertion(credential);
|
|
1418
|
+
switch (assertion["@type"]) {
|
|
1419
|
+
case "dxos.halo.credentials.SpaceGenesis":
|
|
1420
|
+
if (this._genesisCredential) {
|
|
1421
|
+
log.warn("Space already has a genesis credential.", void 0, {
|
|
1422
|
+
"~LogMeta": "~LogMeta",
|
|
1423
|
+
F: __dxlog_file$2,
|
|
1424
|
+
L: 182,
|
|
1425
|
+
S: this
|
|
1426
|
+
});
|
|
1427
|
+
return false;
|
|
1428
|
+
}
|
|
1429
|
+
if (!credential.issuer.equals(this._spaceKey)) {
|
|
1430
|
+
log.warn("Space genesis credential must be issued by space.", void 0, {
|
|
1431
|
+
"~LogMeta": "~LogMeta",
|
|
1432
|
+
F: __dxlog_file$2,
|
|
1433
|
+
L: 186,
|
|
1434
|
+
S: this
|
|
1435
|
+
});
|
|
1436
|
+
return false;
|
|
1437
|
+
}
|
|
1438
|
+
if (!credential.subject.id.equals(this._spaceKey)) {
|
|
1439
|
+
log.warn("Space genesis credential must be issued to space.", void 0, {
|
|
1440
|
+
"~LogMeta": "~LogMeta",
|
|
1441
|
+
F: __dxlog_file$2,
|
|
1442
|
+
L: 190,
|
|
1443
|
+
S: this
|
|
1444
|
+
});
|
|
1445
|
+
return false;
|
|
1446
|
+
}
|
|
1447
|
+
this._genesisCredential = credential;
|
|
1448
|
+
this._tags = assertion.tags ?? [];
|
|
1449
|
+
this._membershipPolicy = assertion.membershipPolicy ?? MembershipPolicy.INVITE;
|
|
1450
|
+
break;
|
|
1451
|
+
case "dxos.halo.credentials.SpaceMember":
|
|
1452
|
+
if (!assertion.spaceKey.equals(this._spaceKey)) break;
|
|
1453
|
+
if (!this._genesisCredential) {
|
|
1454
|
+
log.warn("Space must have a genesis credential before adding members.", void 0, {
|
|
1455
|
+
"~LogMeta": "~LogMeta",
|
|
1456
|
+
F: __dxlog_file$2,
|
|
1457
|
+
L: 205,
|
|
1458
|
+
S: this
|
|
1459
|
+
});
|
|
1460
|
+
return false;
|
|
1461
|
+
}
|
|
1462
|
+
if (!this._canInviteNewMembers(credential.issuer)) {
|
|
1463
|
+
log.warn(`Space member is not authorized to invite new members: ${credential.issuer}`, void 0, {
|
|
1464
|
+
"~LogMeta": "~LogMeta",
|
|
1465
|
+
F: __dxlog_file$2,
|
|
1466
|
+
L: 209,
|
|
1467
|
+
S: this
|
|
1468
|
+
});
|
|
1469
|
+
return false;
|
|
1470
|
+
}
|
|
1471
|
+
await this._members.process(credential);
|
|
1472
|
+
await this._invitations.process(credential);
|
|
1473
|
+
break;
|
|
1474
|
+
case "dxos.halo.credentials.MemberProfile":
|
|
1475
|
+
if (!this._genesisCredential) {
|
|
1476
|
+
log.warn("Space must have a genesis credential before adding members.", void 0, {
|
|
1477
|
+
"~LogMeta": "~LogMeta",
|
|
1478
|
+
F: __dxlog_file$2,
|
|
1479
|
+
L: 220,
|
|
1480
|
+
S: this
|
|
1481
|
+
});
|
|
1482
|
+
return false;
|
|
1483
|
+
}
|
|
1484
|
+
await this._members.process(credential);
|
|
1485
|
+
break;
|
|
1486
|
+
case "dxos.halo.credentials.AdmittedFeed":
|
|
1487
|
+
if (!this._genesisCredential) {
|
|
1488
|
+
log.warn("Space must have a genesis credential before admitting feeds.", void 0, {
|
|
1489
|
+
"~LogMeta": "~LogMeta",
|
|
1490
|
+
F: __dxlog_file$2,
|
|
1491
|
+
L: 230,
|
|
1492
|
+
S: this
|
|
1493
|
+
});
|
|
1494
|
+
return false;
|
|
1495
|
+
}
|
|
1496
|
+
await this._feeds.process(credential, sourceFeed);
|
|
1497
|
+
break;
|
|
1498
|
+
case "dxos.halo.invitations.CancelDelegatedInvitation":
|
|
1499
|
+
case "dxos.halo.invitations.DelegateSpaceInvitation":
|
|
1500
|
+
if (!this._canInviteNewMembers(credential.issuer)) {
|
|
1501
|
+
log.warn(`Invalid invitation, space member is not authorized to invite new members: ${credential.issuer}`, void 0, {
|
|
1502
|
+
"~LogMeta": "~LogMeta",
|
|
1503
|
+
F: __dxlog_file$2,
|
|
1504
|
+
L: 241,
|
|
1505
|
+
S: this
|
|
1506
|
+
});
|
|
1507
|
+
return false;
|
|
1508
|
+
}
|
|
1509
|
+
await this._invitations.process(credential);
|
|
1510
|
+
break;
|
|
1511
|
+
}
|
|
1512
|
+
const newEntry = {
|
|
1513
|
+
credential,
|
|
1514
|
+
sourceFeed,
|
|
1515
|
+
revoked: false
|
|
1516
|
+
};
|
|
1517
|
+
this._credentials.push(newEntry);
|
|
1518
|
+
if (credential.id) this._credentialsById.set(credential.id, newEntry);
|
|
1519
|
+
for (const processor of this._credentialProcessors) if (processor._isReadyForLiveCredentials) await processor._process(credential);
|
|
1520
|
+
await this.onCredentialProcessed.callIfSet(credential);
|
|
1521
|
+
return true;
|
|
1522
|
+
}
|
|
1523
|
+
getMemberRole(memberKey) {
|
|
1524
|
+
return this._members.getRole(memberKey);
|
|
1525
|
+
}
|
|
1526
|
+
hasMembershipManagementPermission(memberKey) {
|
|
1527
|
+
return this._canInviteNewMembers(memberKey);
|
|
1528
|
+
}
|
|
1529
|
+
_canInviteNewMembers(key) {
|
|
1530
|
+
if (this._membershipPolicy === MembershipPolicy.LOCKED) return key.equals(this._spaceKey) && this._members.members.size === 0;
|
|
1531
|
+
return key.equals(this._spaceKey) || this._members.getRole(key) === SpaceMember.Role.ADMIN || this._members.getRole(key) === SpaceMember.Role.OWNER;
|
|
1532
|
+
}
|
|
1533
|
+
};
|
|
1534
|
+
__decorate([synchronized], SpaceStateMachine.prototype, "process", null);
|
|
1535
|
+
var CredentialConsumer = class {
|
|
1536
|
+
processor;
|
|
1537
|
+
_onOpen;
|
|
1538
|
+
_onClose;
|
|
1539
|
+
_ctx = new Context(void 0, {
|
|
1540
|
+
"~LogMeta": "~LogMeta",
|
|
1541
|
+
F: __dxlog_file$2,
|
|
1542
|
+
L: 291
|
|
1543
|
+
});
|
|
1544
|
+
/**
|
|
1545
|
+
* @internal
|
|
1546
|
+
* Processor is ready to process live credentials.
|
|
1547
|
+
* NOTE: Setting this flag before all existing credentials are processed will cause them to be processed out of order.
|
|
1548
|
+
* Set externally.
|
|
1549
|
+
*/
|
|
1550
|
+
_isReadyForLiveCredentials = false;
|
|
1551
|
+
constructor(processor, _onOpen, _onClose) {
|
|
1552
|
+
this.processor = processor;
|
|
1553
|
+
this._onOpen = _onOpen;
|
|
1554
|
+
this._onClose = _onClose;
|
|
1555
|
+
}
|
|
1556
|
+
/**
|
|
1557
|
+
* @internal
|
|
1558
|
+
*/
|
|
1559
|
+
async _process(credential) {
|
|
1560
|
+
await runInContextAsync(this._ctx, async () => {
|
|
1561
|
+
await this.processor.processCredential(credential);
|
|
1562
|
+
});
|
|
1563
|
+
}
|
|
1564
|
+
async open() {
|
|
1565
|
+
if (this._ctx.disposed) throw new Error("CredentialProcessor is disposed");
|
|
1566
|
+
await this._onOpen();
|
|
1567
|
+
}
|
|
1568
|
+
async close() {
|
|
1569
|
+
await this._ctx.dispose();
|
|
1570
|
+
await this._onClose();
|
|
1571
|
+
}
|
|
1572
|
+
};
|
|
1573
|
+
//#endregion
|
|
1574
|
+
//#region src/processor/device-state-machine.ts
|
|
1575
|
+
var __dxlog_file$1 = "/__w/dxos/dxos/packages/core/halo/credentials/src/processor/device-state-machine.ts";
|
|
1576
|
+
/**
|
|
1577
|
+
* Processes device invitation credentials.
|
|
1578
|
+
*/
|
|
1579
|
+
var DeviceStateMachine = class {
|
|
1580
|
+
_params;
|
|
1581
|
+
authorizedDeviceKeys = new ComplexMap(PublicKey.hash);
|
|
1582
|
+
deviceChainReady = new Trigger();
|
|
1583
|
+
deviceCredentialChain;
|
|
1584
|
+
constructor(_params) {
|
|
1585
|
+
this._params = _params;
|
|
1586
|
+
}
|
|
1587
|
+
async processCredential(credential) {
|
|
1588
|
+
log("processing credential...", {
|
|
1589
|
+
identityKey: this._params.identityKey,
|
|
1590
|
+
deviceKey: this._params.deviceKey,
|
|
1591
|
+
credential
|
|
1592
|
+
}, {
|
|
1593
|
+
"~LogMeta": "~LogMeta",
|
|
1594
|
+
F: __dxlog_file$1,
|
|
1595
|
+
L: 35,
|
|
1596
|
+
S: this
|
|
1597
|
+
});
|
|
1598
|
+
if (isValidAuthorizedDeviceCredential(credential, this._params.identityKey, this._params.deviceKey)) {
|
|
1599
|
+
this.deviceCredentialChain = { credential };
|
|
1600
|
+
this.deviceChainReady.wake();
|
|
1601
|
+
}
|
|
1602
|
+
const assertion = getCredentialAssertion(credential);
|
|
1603
|
+
switch (assertion["@type"]) {
|
|
1604
|
+
case "dxos.halo.credentials.AuthorizedDevice":
|
|
1605
|
+
this.authorizedDeviceKeys.set(assertion.deviceKey, this.authorizedDeviceKeys.get(assertion.deviceKey) ?? {});
|
|
1606
|
+
log("added device", {
|
|
1607
|
+
localDeviceKey: this._params.deviceKey,
|
|
1608
|
+
deviceKey: assertion.deviceKey,
|
|
1609
|
+
size: this.authorizedDeviceKeys.size
|
|
1610
|
+
}, {
|
|
1611
|
+
"~LogMeta": "~LogMeta",
|
|
1612
|
+
F: __dxlog_file$1,
|
|
1613
|
+
L: 55,
|
|
1614
|
+
S: this
|
|
1615
|
+
});
|
|
1616
|
+
this._params.onUpdate?.();
|
|
1617
|
+
break;
|
|
1618
|
+
case "dxos.halo.credentials.DeviceProfile":
|
|
1619
|
+
invariant(this.authorizedDeviceKeys.has(credential.subject.id), "Device not found.", {
|
|
1620
|
+
"~LogMeta": "~LogMeta",
|
|
1621
|
+
F: __dxlog_file$1,
|
|
1622
|
+
L: 65,
|
|
1623
|
+
S: this,
|
|
1624
|
+
A: ["this.authorizedDeviceKeys.has(credential.subject.id)", "'Device not found.'"]
|
|
1625
|
+
});
|
|
1626
|
+
if (assertion && credential.subject.id.equals(this._params.deviceKey)) log.trace("dxos.halo.device", {
|
|
1627
|
+
deviceKey: credential.subject.id,
|
|
1628
|
+
profile: assertion.profile
|
|
1629
|
+
}, {
|
|
1630
|
+
"~LogMeta": "~LogMeta",
|
|
1631
|
+
F: __dxlog_file$1,
|
|
1632
|
+
L: 68,
|
|
1633
|
+
S: this
|
|
1634
|
+
});
|
|
1635
|
+
this.authorizedDeviceKeys.set(credential.subject.id, assertion.profile);
|
|
1636
|
+
this._params.onUpdate?.();
|
|
1637
|
+
break;
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
};
|
|
1641
|
+
//#endregion
|
|
1642
|
+
//#region src/processor/profile-state-machine.ts
|
|
1643
|
+
var __dxlog_file = "/__w/dxos/dxos/packages/core/halo/credentials/src/processor/profile-state-machine.ts";
|
|
1644
|
+
/**
|
|
1645
|
+
* Processes device invitation credentials.
|
|
1646
|
+
*/
|
|
1647
|
+
var ProfileStateMachine = class {
|
|
1648
|
+
_params;
|
|
1649
|
+
profile;
|
|
1650
|
+
constructor(_params) {
|
|
1651
|
+
this._params = _params;
|
|
1652
|
+
}
|
|
1653
|
+
async processCredential(credential) {
|
|
1654
|
+
const assertion = getCredentialAssertion(credential);
|
|
1655
|
+
switch (assertion["@type"]) {
|
|
1656
|
+
case "dxos.halo.credentials.IdentityProfile":
|
|
1657
|
+
if (!credential.issuer.equals(this._params.identityKey) || !credential.subject.id.equals(this._params.identityKey)) {
|
|
1658
|
+
log.warn("Invalid profile credential", {
|
|
1659
|
+
expectedIdentity: this._params.identityKey,
|
|
1660
|
+
credential
|
|
1661
|
+
}, {
|
|
1662
|
+
"~LogMeta": "~LogMeta",
|
|
1663
|
+
F: __dxlog_file,
|
|
1664
|
+
L: 34,
|
|
1665
|
+
S: this
|
|
1666
|
+
});
|
|
1667
|
+
return;
|
|
1668
|
+
}
|
|
1669
|
+
this.profile = assertion.profile;
|
|
1670
|
+
log("updated profile", {
|
|
1671
|
+
identityKey: this._params.identityKey,
|
|
1672
|
+
profile: this.profile
|
|
1673
|
+
}, {
|
|
1674
|
+
"~LogMeta": "~LogMeta",
|
|
1675
|
+
F: __dxlog_file,
|
|
1676
|
+
L: 40,
|
|
1677
|
+
S: this
|
|
1678
|
+
});
|
|
1679
|
+
this._params.onUpdate?.();
|
|
1680
|
+
break;
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
};
|
|
1684
|
+
//#endregion
|
|
1685
|
+
export { CredentialGenerator, DeviceStateMachine, FeedStateMachine, InvitationStateMachine, MemberStateMachine, ProfileStateMachine, SIGNATURE_TYPE_ED25519, SpaceStateMachine, canonicalStringify, checkCredentialType, createAdmissionCredentials, createCancelDelegatedSpaceInvitationCredential, createCredential, createCredentialMessage, createCredentialSignerWithChain, createCredentialSignerWithKey, createDelegatedSpaceInvitationCredential, createDeviceAuthorization, createDidFromIdentityKey, credentialTypeFilter, defaultSecretProvider, defaultSecretValidator, generatePasscode, generateSeedPhrase, getCredentialAssertion, getCredentialProofPayload, getPresentationProofPayload, isValidAuthorizedDeviceCredential, keyPairFromSeedPhrase, signPresentation, verifyChain, verifyCredential, verifyCredentialSignature, verifyPresentation, verifyPresentationChain, verifyPresentationSignature };
|
|
1686
|
+
|
|
1687
|
+
//# sourceMappingURL=index.mjs.map
|