@ibgib/core-gib 0.1.43 → 0.1.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/keystone/kdf/kdf-constants.d.mts +25 -0
- package/dist/keystone/kdf/kdf-constants.d.mts.map +1 -0
- package/dist/keystone/kdf/kdf-constants.mjs +28 -0
- package/dist/keystone/kdf/kdf-constants.mjs.map +1 -0
- package/dist/keystone/kdf/kdf-helpers.d.mts +45 -0
- package/dist/keystone/kdf/kdf-helpers.d.mts.map +1 -0
- package/dist/keystone/kdf/kdf-helpers.mjs +94 -0
- package/dist/keystone/kdf/kdf-helpers.mjs.map +1 -0
- package/dist/keystone/kdf/kdf-types.d.mts +49 -0
- package/dist/keystone/kdf/kdf-types.d.mts.map +1 -0
- package/dist/keystone/kdf/kdf-types.mjs +2 -0
- package/dist/keystone/kdf/kdf-types.mjs.map +1 -0
- package/dist/keystone/keystone-config-builder.d.mts +65 -12
- package/dist/keystone/keystone-config-builder.d.mts.map +1 -1
- package/dist/keystone/keystone-config-builder.mjs +138 -46
- package/dist/keystone/keystone-config-builder.mjs.map +1 -1
- package/dist/keystone/keystone-config-builder.respec.mjs +21 -13
- package/dist/keystone/keystone-config-builder.respec.mjs.map +1 -1
- package/dist/keystone/keystone-constants.d.mts +15 -0
- package/dist/keystone/keystone-constants.d.mts.map +1 -1
- package/dist/keystone/keystone-constants.mjs +16 -0
- package/dist/keystone/keystone-constants.mjs.map +1 -1
- package/dist/keystone/keystone-helpers.d.mts +8 -4
- package/dist/keystone/keystone-helpers.d.mts.map +1 -1
- package/dist/keystone/keystone-helpers.mjs +76 -6
- package/dist/keystone/keystone-helpers.mjs.map +1 -1
- package/dist/keystone/keystone-service-v1.d.mts +1 -1
- package/dist/keystone/keystone-service-v1.d.mts.map +1 -1
- package/dist/keystone/keystone-service-v1.mjs +6 -5
- package/dist/keystone/keystone-service-v1.mjs.map +1 -1
- package/dist/keystone/keystone-service-v1.respec.mjs +72 -45
- package/dist/keystone/keystone-service-v1.respec.mjs.map +1 -1
- package/dist/keystone/keystone-types.d.mts +28 -18
- package/dist/keystone/keystone-types.d.mts.map +1 -1
- package/dist/keystone/keystone-types.mjs +26 -15
- package/dist/keystone/keystone-types.mjs.map +1 -1
- package/dist/keystone/strategy/hash-reveal-v1/hash-reveal-v1.d.mts.map +1 -1
- package/dist/keystone/strategy/hash-reveal-v1/hash-reveal-v1.mjs +7 -10
- package/dist/keystone/strategy/hash-reveal-v1/hash-reveal-v1.mjs.map +1 -1
- package/dist/sync/sync-constants.d.mts +9 -0
- package/dist/sync/sync-constants.d.mts.map +1 -1
- package/dist/sync/sync-constants.mjs +10 -0
- package/dist/sync/sync-constants.mjs.map +1 -1
- package/dist/sync/sync-innerspace-dest-ahead-withid.respec.mjs +49 -19
- package/dist/sync/sync-innerspace-dest-ahead-withid.respec.mjs.map +1 -1
- package/dist/sync/sync-peer/sync-peer-v1.mjs +3 -3
- package/dist/sync/sync-peer/sync-peer-v1.mjs.map +1 -1
- package/dist/sync/sync-saga-context/sync-saga-context-helpers.d.mts +0 -38
- package/dist/sync/sync-saga-context/sync-saga-context-helpers.d.mts.map +1 -1
- package/dist/sync/sync-saga-context/sync-saga-context-helpers.mjs +1 -83
- package/dist/sync/sync-saga-context/sync-saga-context-helpers.mjs.map +1 -1
- package/dist/sync/sync-saga-context/sync-saga-context-types.d.mts +24 -4
- package/dist/sync/sync-saga-context/sync-saga-context-types.d.mts.map +1 -1
- package/dist/sync/sync-saga-coordinator.d.mts +36 -13
- package/dist/sync/sync-saga-coordinator.d.mts.map +1 -1
- package/dist/sync/sync-saga-coordinator.mjs +246 -38
- package/dist/sync/sync-saga-coordinator.mjs.map +1 -1
- package/dist/sync/sync-saga-message/sync-saga-message-types.d.mts +1 -7
- package/dist/sync/sync-saga-message/sync-saga-message-types.d.mts.map +1 -1
- package/dist/sync/sync-types.d.mts +11 -0
- package/dist/sync/sync-types.d.mts.map +1 -1
- package/dist/sync/sync-types.mjs.map +1 -1
- package/package.json +1 -1
- package/src/keystone/README.md +4 -3
- package/src/keystone/docs/architecture.md +3 -1
- package/src/keystone/kdf/kdf-constants.mts +34 -0
- package/src/keystone/kdf/kdf-helpers.mts +105 -0
- package/src/keystone/kdf/kdf-types.mts +58 -0
- package/src/keystone/keystone-config-builder.mts +170 -47
- package/src/keystone/keystone-config-builder.respec.mts +21 -14
- package/src/keystone/keystone-constants.mts +21 -2
- package/src/keystone/keystone-helpers.mts +100 -14
- package/src/keystone/keystone-service-v1.mts +23 -22
- package/src/keystone/keystone-service-v1.respec.mts +71 -44
- package/src/keystone/keystone-types.mts +37 -23
- package/src/keystone/strategy/hash-reveal-v1/hash-reveal-v1.mts +9 -13
- package/src/sync/sync-constants.mts +12 -0
- package/src/sync/sync-innerspace-dest-ahead-withid.respec.mts +53 -20
- package/src/sync/sync-peer/sync-peer-v1.mts +3 -3
- package/src/sync/sync-saga-context/sync-saga-context-helpers.mts +3 -107
- package/src/sync/sync-saga-context/sync-saga-context-types.mts +25 -4
- package/src/sync/sync-saga-coordinator.mts +313 -40
- package/src/sync/sync-saga-message/sync-saga-message-types.mts +1 -7
- package/src/sync/sync-types.mts +12 -0
- package/tmp.md +0 -274
|
@@ -20,7 +20,7 @@ const logalot = GLOBAL_LOG_A_LOT || true;
|
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* Facade for managing Keystone Identities.
|
|
23
|
-
*
|
|
23
|
+
*
|
|
24
24
|
* Handles Genesis, Authorized Evolution (Signing), and Validation.
|
|
25
25
|
*/
|
|
26
26
|
export class KeystoneService_V1 {
|
|
@@ -93,9 +93,9 @@ export class KeystoneService_V1 {
|
|
|
93
93
|
|
|
94
94
|
/**
|
|
95
95
|
* Signs a claim by solving challenges from a specific pool and evolving the Keystone timeline.
|
|
96
|
-
*
|
|
96
|
+
*
|
|
97
97
|
* Uses a hybrid selection strategy: Mandatory IDs (Alice) + Sequential (FIFO) + Random (Stochastic).
|
|
98
|
-
*
|
|
98
|
+
*
|
|
99
99
|
* Supports Delegation via `poolFilter` to find specific foreign pools.
|
|
100
100
|
*/
|
|
101
101
|
async sign({
|
|
@@ -122,7 +122,7 @@ export class KeystoneService_V1 {
|
|
|
122
122
|
*/
|
|
123
123
|
poolId?: string;
|
|
124
124
|
/**
|
|
125
|
-
* Optional predicate to find a pool.
|
|
125
|
+
* Optional predicate to find a pool.
|
|
126
126
|
* Useful for finding delegates via metadata without knowing the exact ID.
|
|
127
127
|
* e.g. (p) => p.metadata?.delegate === 'Bob'
|
|
128
128
|
*/
|
|
@@ -158,7 +158,7 @@ export class KeystoneService_V1 {
|
|
|
158
158
|
});
|
|
159
159
|
|
|
160
160
|
// 3. Pay the Cost (Solve & Replenish)
|
|
161
|
-
// This helper handles the Strategy creation, Secret derivation, Solving,
|
|
161
|
+
// This helper handles the Strategy creation, Secret derivation, Solving,
|
|
162
162
|
// and the calculation of the Next state for ALL pools.
|
|
163
163
|
const { proof, nextPools } = await solveAndReplenish({
|
|
164
164
|
targetPoolId: pool.id,
|
|
@@ -196,13 +196,13 @@ export class KeystoneService_V1 {
|
|
|
196
196
|
|
|
197
197
|
/**
|
|
198
198
|
* Validates a keystone.
|
|
199
|
-
*
|
|
199
|
+
*
|
|
200
200
|
* ## NOTES
|
|
201
|
-
*
|
|
201
|
+
*
|
|
202
202
|
* Atow (12/22/2025) this only validates the transition from Prev -> Curr.
|
|
203
|
-
*
|
|
203
|
+
*
|
|
204
204
|
* @returns Array of validation error strings. Empty array means Valid.
|
|
205
|
-
*
|
|
205
|
+
*
|
|
206
206
|
* @see {@link validateKeystoneTransition}
|
|
207
207
|
*/
|
|
208
208
|
async validate({
|
|
@@ -220,11 +220,11 @@ export class KeystoneService_V1 {
|
|
|
220
220
|
|
|
221
221
|
/**
|
|
222
222
|
* Permanently revokes the Identity.
|
|
223
|
-
*
|
|
223
|
+
*
|
|
224
224
|
* Logic:
|
|
225
225
|
* 1. Locates the 'revoke' pool.
|
|
226
226
|
* 2. Solves required challenges to prove ownership.
|
|
227
|
-
* 3. Wipes the pool (via
|
|
227
|
+
* 3. Wipes the pool (via the delete all strategy in solveAndReplenish).
|
|
228
228
|
* 4. Sets the revocationInfo on the new frame.
|
|
229
229
|
*/
|
|
230
230
|
async revoke({
|
|
@@ -270,8 +270,9 @@ export class KeystoneService_V1 {
|
|
|
270
270
|
if (idsToSolve.length === 0) { throw new Error(`Revocation policy selected 0 challenges? Check config for pool ${pool.id}. Revocation requires proof. (E: 97e5a8356d241ae7b882db791cb1f825)`); }
|
|
271
271
|
|
|
272
272
|
// 4. Pay the Cost & Scorched Earth
|
|
273
|
-
// The revoke pool config should have '
|
|
274
|
-
// causing solveAndReplenish
|
|
273
|
+
// The revoke pool config should have 'replenishStrategy:
|
|
274
|
+
// KeystoneReplenishStrategy.deleteAll', causing solveAndReplenish
|
|
275
|
+
// to return an empty pool in nextPools.
|
|
275
276
|
const { proof, nextPools } = await solveAndReplenish({
|
|
276
277
|
targetPoolId: pool.id,
|
|
277
278
|
prevPools: prevData.challengePools,
|
|
@@ -283,7 +284,7 @@ export class KeystoneService_V1 {
|
|
|
283
284
|
// warn if nextPools contains pool.id that isn't empty (we were
|
|
284
285
|
// supposed to do "scorched earth" which empties the pool)
|
|
285
286
|
if (nextPools.find(p => p.id === pool.id && Object.keys(p.challenges).length > 0)) {
|
|
286
|
-
console.warn(`${lc} revocation pool ${pool.id} is not empty after revocation. Is the revocation pool replenish strategy set to ${KeystoneReplenishStrategy.
|
|
287
|
+
console.warn(`${lc} revocation pool ${pool.id} is not empty after revocation. Is the revocation pool replenish strategy set to ${KeystoneReplenishStrategy.deleteAll}? (W: 300c28bc8b98fc3e3c0b0d988344f825)`);
|
|
287
288
|
}
|
|
288
289
|
|
|
289
290
|
// 5. Construct Revocation Info
|
|
@@ -316,11 +317,11 @@ export class KeystoneService_V1 {
|
|
|
316
317
|
|
|
317
318
|
/**
|
|
318
319
|
* Structural evolution: Adds new challenge pools to the keystone.
|
|
319
|
-
*
|
|
320
|
-
* Use Case: Adding a delegate (Server) for SSO, adding a recovery key,
|
|
320
|
+
*
|
|
321
|
+
* Use Case: Adding a delegate (Server) for SSO, adding a recovery key,
|
|
321
322
|
* or rotating to a new set of pools.
|
|
322
|
-
*
|
|
323
|
-
* Requires the Master Secret to authorize the change via a pool containing
|
|
323
|
+
*
|
|
324
|
+
* Requires the Master Secret to authorize the change via a pool containing
|
|
324
325
|
* the 'manage' verb.
|
|
325
326
|
*/
|
|
326
327
|
async addPools({
|
|
@@ -332,14 +333,14 @@ export class KeystoneService_V1 {
|
|
|
332
333
|
}: {
|
|
333
334
|
latestKeystone: KeystoneIbGib_V1;
|
|
334
335
|
/**
|
|
335
|
-
* Alice's Master Secret.
|
|
336
|
+
* Alice's Master Secret.
|
|
336
337
|
* Required to solve challenges from the Admin/Manage pool to authorize this change.
|
|
337
338
|
*/
|
|
338
339
|
masterSecret: string;
|
|
339
340
|
/**
|
|
340
|
-
* The pools to add.
|
|
341
|
-
* NOTE: These are fully constructed Pool objects.
|
|
342
|
-
* If they are foreign (Bob's), Alice must have constructed them
|
|
341
|
+
* The pools to add.
|
|
342
|
+
* NOTE: These are fully constructed Pool objects.
|
|
343
|
+
* If they are foreign (Bob's), Alice must have constructed them
|
|
343
344
|
* using Bob's challenges + Her config restrictions + isForeign=true.
|
|
344
345
|
*/
|
|
345
346
|
newPools: KeystoneChallengePool[];
|
|
@@ -195,12 +195,15 @@ await respecfully(sir, 'Suite A: Strategy Vectors (HashRevealV1)', async () => {
|
|
|
195
195
|
|
|
196
196
|
firstOfAll(sir, async () => {
|
|
197
197
|
// Use our standard builder to get a valid config object
|
|
198
|
-
config = createStandardPoolConfig(
|
|
198
|
+
config = createStandardPoolConfig({
|
|
199
|
+
id: salt,
|
|
200
|
+
salt,
|
|
201
|
+
}) as KeystonePoolConfig_HashV1;
|
|
199
202
|
});
|
|
200
203
|
|
|
201
204
|
await respecfully(sir, 'Derivation Logic', async () => {
|
|
202
205
|
|
|
203
|
-
await
|
|
206
|
+
await ifWeMight(sir, 'derivePoolSecret with same inputs returns same output', async () => {
|
|
204
207
|
const strategy = KeystoneStrategyFactory.create({ config });
|
|
205
208
|
|
|
206
209
|
const secretA = await strategy.derivePoolSecret({ masterSecret });
|
|
@@ -210,7 +213,7 @@ await respecfully(sir, 'Suite A: Strategy Vectors (HashRevealV1)', async () => {
|
|
|
210
213
|
iReckon(sir, secretA).asTo('secret length').isGonnaBeTruthy();
|
|
211
214
|
});
|
|
212
215
|
|
|
213
|
-
await
|
|
216
|
+
await ifWeMight(sir, 'derivePoolSecret with different master secret returns different output', async () => {
|
|
214
217
|
const strategy = KeystoneStrategyFactory.create({ config });
|
|
215
218
|
|
|
216
219
|
const secretA = await strategy.derivePoolSecret({ masterSecret });
|
|
@@ -219,7 +222,7 @@ await respecfully(sir, 'Suite A: Strategy Vectors (HashRevealV1)', async () => {
|
|
|
219
222
|
iReckon(sir, secretA).asTo('secrets differ').not.willEqual(secretB);
|
|
220
223
|
});
|
|
221
224
|
|
|
222
|
-
await
|
|
225
|
+
await ifWeMight(sir, 'derivePoolSecret with different salt returns different output', async () => {
|
|
223
226
|
// Modify salt in a copy of config
|
|
224
227
|
const configB = { ...config, salt: "OtherPool" };
|
|
225
228
|
const strategyA = KeystoneStrategyFactory.create({ config });
|
|
@@ -234,7 +237,7 @@ await respecfully(sir, 'Suite A: Strategy Vectors (HashRevealV1)', async () => {
|
|
|
234
237
|
|
|
235
238
|
await respecfully(sir, 'Challenge/Solution Logic', async () => {
|
|
236
239
|
|
|
237
|
-
await
|
|
240
|
+
await ifWeMight(sir, 'generateSolution -> generateChallenge -> validateSolution loop works', async () => {
|
|
238
241
|
const strategy = KeystoneStrategyFactory.create({ config });
|
|
239
242
|
const poolSecret = await strategy.derivePoolSecret({ masterSecret });
|
|
240
243
|
const challengeId = "a3ff7843552870fc28bef2b"; // arbitrary random challengeId
|
|
@@ -253,7 +256,7 @@ await respecfully(sir, 'Suite A: Strategy Vectors (HashRevealV1)', async () => {
|
|
|
253
256
|
iReckon(sir, isValid).asTo('valid pair should pass').isGonnaBeTrue();
|
|
254
257
|
});
|
|
255
258
|
|
|
256
|
-
await
|
|
259
|
+
await ifWeMight(sir, 'validateSolution fails for mismatched values', async () => {
|
|
257
260
|
const strategy = KeystoneStrategyFactory.create({ config });
|
|
258
261
|
const poolSecret = await strategy.derivePoolSecret({ masterSecret });
|
|
259
262
|
const challengeId = "8c994f3ed598f150e25513"; // arbitrary random challengeId
|
|
@@ -269,7 +272,7 @@ await respecfully(sir, 'Suite A: Strategy Vectors (HashRevealV1)', async () => {
|
|
|
269
272
|
iReckon(sir, isValid).asTo('tampered solution should fail').isGonnaBeFalse();
|
|
270
273
|
});
|
|
271
274
|
|
|
272
|
-
await
|
|
275
|
+
await ifWeMight(sir, 'validateSolution fails for mismatched challenge hashes', async () => {
|
|
273
276
|
const strategy = KeystoneStrategyFactory.create({ config });
|
|
274
277
|
const poolSecret = await strategy.derivePoolSecret({ masterSecret });
|
|
275
278
|
|
|
@@ -310,8 +313,11 @@ await respecfully(sir, 'Suite B: Service Lifecycle', async () => {
|
|
|
310
313
|
});
|
|
311
314
|
|
|
312
315
|
await respecfully(sir, 'Genesis', async () => {
|
|
313
|
-
await
|
|
314
|
-
const config = createStandardPoolConfig(
|
|
316
|
+
await ifWeMight(sir, 'creates a valid genesis frame and persists it', async () => {
|
|
317
|
+
const config = createStandardPoolConfig({
|
|
318
|
+
id: POOL_ID_DEFAULT,
|
|
319
|
+
salt: POOL_ID_DEFAULT,
|
|
320
|
+
});
|
|
315
321
|
|
|
316
322
|
genesisKeystone = await service.genesis({
|
|
317
323
|
masterSecret,
|
|
@@ -338,7 +344,7 @@ await respecfully(sir, 'Suite B: Service Lifecycle', async () => {
|
|
|
338
344
|
});
|
|
339
345
|
|
|
340
346
|
await respecfully(sir, 'Signing (Evolution)', async () => {
|
|
341
|
-
await
|
|
347
|
+
await ifWeMight(sir, 'evolves the keystone with a valid proof', async () => {
|
|
342
348
|
const claim: Partial<KeystoneClaim> = {
|
|
343
349
|
target: "comment 123^gib",
|
|
344
350
|
verb: "post"
|
|
@@ -368,7 +374,7 @@ await respecfully(sir, 'Suite B: Service Lifecycle', async () => {
|
|
|
368
374
|
});
|
|
369
375
|
|
|
370
376
|
await respecfully(sir, 'Validation', async () => {
|
|
371
|
-
await
|
|
377
|
+
await ifWeMight(sir, 'validates the genesis->signed transition', async () => {
|
|
372
378
|
const errors = await service.validate({
|
|
373
379
|
prevIbGib: genesisKeystone,
|
|
374
380
|
currentIbGib: signedKeystone,
|
|
@@ -400,8 +406,13 @@ await respecfully(sir, 'Suite C: Security Vectors', async () => {
|
|
|
400
406
|
mockMetaspace = new MockMetaspaceService(mockSpace);
|
|
401
407
|
|
|
402
408
|
// Setup Alice's Identity
|
|
403
|
-
const config = createStandardPoolConfig(
|
|
404
|
-
|
|
409
|
+
const config = createStandardPoolConfig({
|
|
410
|
+
id: POOL_ID_DEFAULT,
|
|
411
|
+
salt: POOL_ID_DEFAULT,
|
|
412
|
+
targetBinding: 0,
|
|
413
|
+
size: 10,
|
|
414
|
+
});
|
|
415
|
+
// config.behavior.size = 10;
|
|
405
416
|
genesisKeystone = await service.genesis({
|
|
406
417
|
masterSecret: aliceSecret,
|
|
407
418
|
configs: [config],
|
|
@@ -411,7 +422,7 @@ await respecfully(sir, 'Suite C: Security Vectors', async () => {
|
|
|
411
422
|
});
|
|
412
423
|
|
|
413
424
|
await respecfully(sir, 'Wrong Secret (Forgery)', async () => {
|
|
414
|
-
await
|
|
425
|
+
await ifWeMight(sir, 'prevents creation of forged frames', async () => {
|
|
415
426
|
const claim: Partial<KeystoneClaim> = { target: "comment 123^gib", verb: "post" };
|
|
416
427
|
|
|
417
428
|
let errorCaught = false;
|
|
@@ -440,10 +451,13 @@ await respecfully(sir, 'Suite C: Security Vectors', async () => {
|
|
|
440
451
|
});
|
|
441
452
|
|
|
442
453
|
await respecfully(sir, 'Policy Violation (Restricted Verbs)', async () => {
|
|
443
|
-
await
|
|
454
|
+
await ifWeMight(sir, 'throws error if signing forbidden verb with restricted pool', async () => {
|
|
444
455
|
// Create a specific restricted pool config manually
|
|
445
456
|
const restrictedPoolId = "read_only_pool";
|
|
446
|
-
const restrictedConfig = createStandardPoolConfig(
|
|
457
|
+
const restrictedConfig = createStandardPoolConfig({
|
|
458
|
+
id: restrictedPoolId,
|
|
459
|
+
salt: restrictedPoolId,
|
|
460
|
+
});
|
|
447
461
|
// Manually restrict it (since Builder defaults to undefined/allow-all)
|
|
448
462
|
restrictedConfig.allowedVerbs = ['read'];
|
|
449
463
|
|
|
@@ -495,8 +509,14 @@ await respecfully(sir, 'Suite D: Revocation', async () => {
|
|
|
495
509
|
mockMetaspace = new MockMetaspaceService(mockSpace);
|
|
496
510
|
|
|
497
511
|
// Setup Identity WITH a Revocation Pool
|
|
498
|
-
const stdConfig = createStandardPoolConfig(
|
|
499
|
-
|
|
512
|
+
const stdConfig = createStandardPoolConfig({
|
|
513
|
+
id: POOL_ID_DEFAULT,
|
|
514
|
+
salt: POOL_ID_DEFAULT,
|
|
515
|
+
});
|
|
516
|
+
const revokeConfig = createRevocationPoolConfig({
|
|
517
|
+
id: POOL_ID_REVOKE,
|
|
518
|
+
salt: POOL_ID_REVOKE,
|
|
519
|
+
}); // Special Config
|
|
500
520
|
|
|
501
521
|
genesisKeystone = await service.genesis({
|
|
502
522
|
masterSecret,
|
|
@@ -509,7 +529,7 @@ await respecfully(sir, 'Suite D: Revocation', async () => {
|
|
|
509
529
|
await respecfully(sir, 'Revoke Lifecycle', async () => {
|
|
510
530
|
let revokedKeystone: KeystoneIbGib_V1;
|
|
511
531
|
|
|
512
|
-
await
|
|
532
|
+
await ifWeMight(sir, 'successfully creates a revocation frame', async () => {
|
|
513
533
|
revokedKeystone = await service.revoke({
|
|
514
534
|
latestKeystone: genesisKeystone,
|
|
515
535
|
masterSecret,
|
|
@@ -527,7 +547,7 @@ await respecfully(sir, 'Suite D: Revocation', async () => {
|
|
|
527
547
|
iReckon(sir, data.revocationInfo!.proof.claim.verb).willEqual(KEYSTONE_VERB_REVOKE);
|
|
528
548
|
});
|
|
529
549
|
|
|
530
|
-
await
|
|
550
|
+
await ifWeMight(sir, 'validates the revocation frame', async () => {
|
|
531
551
|
const errors = await service.validate({
|
|
532
552
|
prevIbGib: genesisKeystone,
|
|
533
553
|
currentIbGib: revokedKeystone!,
|
|
@@ -538,7 +558,7 @@ await respecfully(sir, 'Suite D: Revocation', async () => {
|
|
|
538
558
|
iReckon(sir, errors.length).asTo('no validation errors').willEqual(0);
|
|
539
559
|
});
|
|
540
560
|
|
|
541
|
-
await
|
|
561
|
+
await ifWeMight(sir, 'consumed the revocation pool (Scorched Earth)', async () => {
|
|
542
562
|
const data = revokedKeystone!.data!;
|
|
543
563
|
const revokePool = data.challengePools.find(p => p.id === POOL_ID_REVOKE);
|
|
544
564
|
|
|
@@ -568,7 +588,7 @@ await respecfully(sir, 'Suite E: Structural Evolution (addPools)', async () => {
|
|
|
568
588
|
|
|
569
589
|
// Helper to generate a "Foreign" pool (e.g. from Bob)
|
|
570
590
|
const createForeignPool = async (id: string, verbs: string[] = []): Promise<KeystoneChallengePool> => {
|
|
571
|
-
const config = createStandardPoolConfig(id);
|
|
591
|
+
const config = createStandardPoolConfig({ id, salt: id });
|
|
572
592
|
config.allowedVerbs = verbs;
|
|
573
593
|
|
|
574
594
|
// We use the factory manually here to simulate Bob doing this offline
|
|
@@ -605,7 +625,7 @@ await respecfully(sir, 'Suite E: Structural Evolution (addPools)', async () => {
|
|
|
605
625
|
mockMetaspace = new MockMetaspaceService(mockSpace);
|
|
606
626
|
|
|
607
627
|
// Alice Genesis: Standard pool (allows all verbs, including 'manage')
|
|
608
|
-
const config = createStandardPoolConfig(POOL_ID_DEFAULT);
|
|
628
|
+
const config = createStandardPoolConfig({ id: POOL_ID_DEFAULT, salt: POOL_ID_DEFAULT });
|
|
609
629
|
aliceKeystone = await service.genesis({
|
|
610
630
|
masterSecret: aliceSecret,
|
|
611
631
|
configs: [config],
|
|
@@ -615,7 +635,7 @@ await respecfully(sir, 'Suite E: Structural Evolution (addPools)', async () => {
|
|
|
615
635
|
});
|
|
616
636
|
|
|
617
637
|
await respecfully(sir, 'Happy Path', async () => {
|
|
618
|
-
await
|
|
638
|
+
await ifWeMight(sir, 'authorizes and adds a foreign pool', async () => {
|
|
619
639
|
const bobPool = await createForeignPool("pool_bob", ["post"]);
|
|
620
640
|
|
|
621
641
|
const updatedKeystone = await service.addPools({
|
|
@@ -654,9 +674,10 @@ await respecfully(sir, 'Suite E: Structural Evolution (addPools)', async () => {
|
|
|
654
674
|
});
|
|
655
675
|
|
|
656
676
|
await respecfully(sir, 'Permissions & Logic', async () => {
|
|
657
|
-
await
|
|
677
|
+
await ifWeMight(sir, 'fails if no pool allows "manage" verb', async () => {
|
|
658
678
|
// 1. Create a restricted keystone
|
|
659
|
-
|
|
679
|
+
let id = "read_only";
|
|
680
|
+
const restrictedConfig = createStandardPoolConfig({ id, salt: id });
|
|
660
681
|
restrictedConfig.allowedVerbs = ['read']; // No 'manage'
|
|
661
682
|
|
|
662
683
|
const restrictedKeystone = await service.genesis({
|
|
@@ -686,7 +707,7 @@ await respecfully(sir, 'Suite E: Structural Evolution (addPools)', async () => {
|
|
|
686
707
|
iReckon(sir, errorCaught).asTo('permission denied').isGonnaBeTrue();
|
|
687
708
|
});
|
|
688
709
|
|
|
689
|
-
await
|
|
710
|
+
await ifWeMight(sir, 'fails on ID collision', async () => {
|
|
690
711
|
// Try to add "pool_bob" again (it was added in Happy Path)
|
|
691
712
|
const duplicatePool = await createForeignPool("pool_bob");
|
|
692
713
|
|
|
@@ -725,7 +746,7 @@ await respecfully(sir, 'Suite E: Structural Evolution (addPools)', async () => {
|
|
|
725
746
|
|
|
726
747
|
// Helper to simulate Bob creating a pool "offline" to give to Alice
|
|
727
748
|
const createForeignPool = async (id: string, verbs: string[] = []): Promise<KeystoneChallengePool> => {
|
|
728
|
-
const config = createStandardPoolConfig(id);
|
|
749
|
+
const config = createStandardPoolConfig({ id, salt: id });
|
|
729
750
|
config.allowedVerbs = verbs;
|
|
730
751
|
|
|
731
752
|
// We use the factory manually here to simulate Bob doing this on his own machine
|
|
@@ -762,7 +783,10 @@ await respecfully(sir, 'Suite E: Structural Evolution (addPools)', async () => {
|
|
|
762
783
|
mockMetaspace = new MockMetaspaceService(mockSpace);
|
|
763
784
|
|
|
764
785
|
// Alice Genesis: Standard pool (allows all verbs, including 'manage')
|
|
765
|
-
const config = createStandardPoolConfig(
|
|
786
|
+
const config = createStandardPoolConfig({
|
|
787
|
+
id: POOL_ID_DEFAULT,
|
|
788
|
+
salt: POOL_ID_DEFAULT,
|
|
789
|
+
});
|
|
766
790
|
aliceKeystone = await service.genesis({
|
|
767
791
|
masterSecret: aliceSecret,
|
|
768
792
|
configs: [config],
|
|
@@ -772,7 +796,7 @@ await respecfully(sir, 'Suite E: Structural Evolution (addPools)', async () => {
|
|
|
772
796
|
});
|
|
773
797
|
|
|
774
798
|
await respecfully(sir, 'Happy Path', async () => {
|
|
775
|
-
await
|
|
799
|
+
await ifWeMight(sir, 'authorizes and adds a foreign pool', async () => {
|
|
776
800
|
const bobPool = await createForeignPool("pool_bob", ["post"]);
|
|
777
801
|
|
|
778
802
|
const updatedKeystone = await service.addPools({
|
|
@@ -811,9 +835,10 @@ await respecfully(sir, 'Suite E: Structural Evolution (addPools)', async () => {
|
|
|
811
835
|
});
|
|
812
836
|
|
|
813
837
|
await respecfully(sir, 'Permissions & Logic', async () => {
|
|
814
|
-
await
|
|
838
|
+
await ifWeMight(sir, 'fails if no pool allows "manage" verb', async () => {
|
|
815
839
|
// 1. Create a restricted keystone (read-only)
|
|
816
|
-
|
|
840
|
+
let id = "read_only";
|
|
841
|
+
const restrictedConfig = createStandardPoolConfig({ id, salt: id });
|
|
817
842
|
restrictedConfig.allowedVerbs = ['read']; // No 'manage'
|
|
818
843
|
|
|
819
844
|
const restrictedKeystone = await service.genesis({
|
|
@@ -843,7 +868,7 @@ await respecfully(sir, 'Suite E: Structural Evolution (addPools)', async () => {
|
|
|
843
868
|
iReckon(sir, errorCaught).asTo('permission denied').isGonnaBeTrue();
|
|
844
869
|
});
|
|
845
870
|
|
|
846
|
-
await
|
|
871
|
+
await ifWeMight(sir, 'fails on ID collision', async () => {
|
|
847
872
|
// Try to add "pool_bob" again (it was added in Happy Path)
|
|
848
873
|
const duplicatePool = await createForeignPool("pool_bob");
|
|
849
874
|
|
|
@@ -883,11 +908,13 @@ await respecfully(sir, 'Suite F: Deep Inspection', async () => {
|
|
|
883
908
|
let signedKeystone: KeystoneIbGib_V1;
|
|
884
909
|
|
|
885
910
|
// We use a specific hybrid config to test exact selection logic
|
|
886
|
-
const hybridConfig = createStandardPoolConfig(
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
911
|
+
const hybridConfig = createStandardPoolConfig({
|
|
912
|
+
id: salt,
|
|
913
|
+
salt,
|
|
914
|
+
// 2 FIFO + 2 Random = 4 Total per sign
|
|
915
|
+
sequential: 2, random: 2, targetBinding: 0,
|
|
916
|
+
size: 20, // Small enough to track, large enough to be random
|
|
917
|
+
}) as KeystonePoolConfig_HashV1;
|
|
891
918
|
|
|
892
919
|
firstOfAll(sir, async () => {
|
|
893
920
|
mockSpace = new MockIbGibSpace();
|
|
@@ -903,7 +930,7 @@ await respecfully(sir, 'Suite F: Deep Inspection', async () => {
|
|
|
903
930
|
|
|
904
931
|
await respecfully(sir, 'Proof Granularity & Math', async () => {
|
|
905
932
|
|
|
906
|
-
await
|
|
933
|
+
await ifWeMight(sir, 'generates exactly the expected number of solutions', async () => {
|
|
907
934
|
signedKeystone = await service.sign({
|
|
908
935
|
latestKeystone: genesisKeystone,
|
|
909
936
|
masterSecret: aliceSecret,
|
|
@@ -920,7 +947,7 @@ await respecfully(sir, 'Suite F: Deep Inspection', async () => {
|
|
|
920
947
|
iReckon(sir, solutions.length).asTo('solution count').willEqual(4);
|
|
921
948
|
});
|
|
922
949
|
|
|
923
|
-
await
|
|
950
|
+
await ifWeMight(sir, 'verifies the math manually (White-box Crypto Check)', async () => {
|
|
924
951
|
const proof = signedKeystone.data!.proofs[0];
|
|
925
952
|
const poolSnapshot = genesisKeystone.data!.challengePools.find(p => p.id === salt)!;
|
|
926
953
|
|
|
@@ -949,7 +976,7 @@ await respecfully(sir, 'Suite F: Deep Inspection', async () => {
|
|
|
949
976
|
}
|
|
950
977
|
});
|
|
951
978
|
|
|
952
|
-
await
|
|
979
|
+
await ifWeMight(sir, 'verifies FIFO logic (Deterministic Selection)', async () => {
|
|
953
980
|
const proof = signedKeystone.data!.proofs[0];
|
|
954
981
|
const poolSnapshot = genesisKeystone.data!.challengePools.find(p => p.id === salt)!;
|
|
955
982
|
|
|
@@ -971,7 +998,7 @@ await respecfully(sir, 'Suite F: Deep Inspection', async () => {
|
|
|
971
998
|
|
|
972
999
|
await respecfully(sir, 'DTO & Serialization', async () => {
|
|
973
1000
|
|
|
974
|
-
await
|
|
1001
|
+
await ifWeMight(sir, 'survives a clone/JSON-cycle without corruption', async () => {
|
|
975
1002
|
// 1. Create a DTO (simulate network transmission/storage)
|
|
976
1003
|
// 'clone' does a JSON stringify/parse under the hood (usually) or structured clone.
|
|
977
1004
|
const dto = clone(signedKeystone);
|
|
@@ -991,7 +1018,7 @@ await respecfully(sir, 'Suite F: Deep Inspection', async () => {
|
|
|
991
1018
|
iReckon(sir, errors.length).asTo('DTO validation errors').willEqual(0);
|
|
992
1019
|
});
|
|
993
1020
|
|
|
994
|
-
await
|
|
1021
|
+
await ifWeMight(sir, 'ensures data contains no functions or circular refs', async () => {
|
|
995
1022
|
// A crude but effective test: ensure JSON.stringify doesn't throw
|
|
996
1023
|
// and the result is equal to the object (if we parsed it back).
|
|
997
1024
|
|
|
@@ -1005,7 +1032,7 @@ await respecfully(sir, 'Suite F: Deep Inspection', async () => {
|
|
|
1005
1032
|
iReckon(sir, parsedSolution).asTo('deep property survives stringify').willEqual(originalSolution);
|
|
1006
1033
|
|
|
1007
1034
|
// Ensure no extra properties were lost
|
|
1008
|
-
// FIX: JSON.stringify removes keys with 'undefined' values.
|
|
1035
|
+
// FIX: JSON.stringify removes keys with 'undefined' values.
|
|
1009
1036
|
// We must filter the original keys to match this behavior for a fair comparison.
|
|
1010
1037
|
const origKeys = Object.keys(signedKeystone.data!)
|
|
1011
1038
|
.filter(k => (signedKeystone.data as any)[k] !== undefined);
|
|
@@ -2,15 +2,25 @@ import { IbGib_V1, IbGibData_V1, IbGibRel8ns_V1 } from "@ibgib/ts-gib/dist/V1/ty
|
|
|
2
2
|
|
|
3
3
|
import { KEYSTONE_ATOM } from "./keystone-constants.mjs";
|
|
4
4
|
|
|
5
|
+
// #region KeystoneChallengeType
|
|
6
|
+
export const KEYSTONE_CHALLENGE_TYPE_HASH_REVEAL_V1 = 'hash-reveal-v1';
|
|
5
7
|
/**
|
|
6
8
|
* The discriminator for the mechanism.
|
|
7
9
|
* 'hash-reveal-v1': Standard Hash chain (Sigma-like).
|
|
8
10
|
*/
|
|
9
11
|
export type KeystoneChallengeType =
|
|
10
|
-
|
|
|
12
|
+
| typeof KEYSTONE_CHALLENGE_TYPE_HASH_REVEAL_V1
|
|
11
13
|
// | 'decrypt-v1' // Future
|
|
12
14
|
// | 'pow-v1'; // Future
|
|
13
15
|
;
|
|
16
|
+
export const KeystoneChallengeType = {
|
|
17
|
+
hash_reveal_v1: KEYSTONE_CHALLENGE_TYPE_HASH_REVEAL_V1,
|
|
18
|
+
} satisfies { [key: string]: KeystoneChallengeType };
|
|
19
|
+
export const KEYSTONE_CHALLENGE_TYPE_VALID_VALUES = Object.values(KeystoneChallengeType);
|
|
20
|
+
export function isValidKeystoneChallengeType(x: any): x is KeystoneChallengeType {
|
|
21
|
+
return typeof x === 'string' && KEYSTONE_CHALLENGE_TYPE_VALID_VALUES.includes(x as any);
|
|
22
|
+
}
|
|
23
|
+
// #endregion KeystoneChallengeType
|
|
14
24
|
|
|
15
25
|
// ===========================================================================
|
|
16
26
|
// CONFIGURATION
|
|
@@ -18,55 +28,56 @@ export type KeystoneChallengeType =
|
|
|
18
28
|
|
|
19
29
|
// #region KeystoneReplenishStrategy
|
|
20
30
|
/**
|
|
21
|
-
*
|
|
31
|
+
* @see {@link KeystoneReplenishStrategy.topUp}
|
|
22
32
|
*/
|
|
23
33
|
export const KEYSTONE_REPLENISH_STRATEGY_TOP_UP = 'top-up';
|
|
24
34
|
/**
|
|
25
|
-
*
|
|
35
|
+
* @see {@link KeystoneReplenishStrategy.replaceAll}
|
|
26
36
|
*/
|
|
27
37
|
export const KEYSTONE_REPLENISH_STRATEGY_REPLACE_ALL = 'replace-all';
|
|
28
38
|
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* ## intent
|
|
32
|
-
* adding this for revocation
|
|
39
|
+
* @see {@link KeystoneReplenishStrategy.consume}
|
|
33
40
|
*/
|
|
34
41
|
export const KEYSTONE_REPLENISH_STRATEGY_CONSUME = 'consume';
|
|
35
42
|
/**
|
|
36
|
-
*
|
|
37
|
-
* The "Nuclear Option" for revocation.
|
|
43
|
+
* @see {@link KeystoneReplenishStrategy.deleteAll}
|
|
38
44
|
*/
|
|
39
|
-
export const
|
|
45
|
+
export const KEYSTONE_REPLENISH_STRATEGY_DELETE_ALL = 'delete-all';
|
|
40
46
|
export type KeystoneReplenishStrategy =
|
|
41
47
|
| typeof KEYSTONE_REPLENISH_STRATEGY_TOP_UP
|
|
42
48
|
| typeof KEYSTONE_REPLENISH_STRATEGY_REPLACE_ALL
|
|
43
49
|
| typeof KEYSTONE_REPLENISH_STRATEGY_CONSUME
|
|
44
|
-
| typeof
|
|
50
|
+
| typeof KEYSTONE_REPLENISH_STRATEGY_DELETE_ALL
|
|
45
51
|
;
|
|
46
52
|
/**
|
|
47
53
|
* @see {@link KeystonePoolBehavior.replenish}
|
|
48
54
|
*/
|
|
49
55
|
export const KeystoneReplenishStrategy = {
|
|
50
56
|
/**
|
|
51
|
-
*
|
|
57
|
+
* replaces each used challenge, "topping up" the pool to the pool's size
|
|
52
58
|
*/
|
|
53
59
|
topUp: KEYSTONE_REPLENISH_STRATEGY_TOP_UP,
|
|
54
60
|
/**
|
|
55
|
-
*
|
|
61
|
+
* replaces the entire pool with the new challenges
|
|
56
62
|
*/
|
|
57
63
|
replaceAll: KEYSTONE_REPLENISH_STRATEGY_REPLACE_ALL,
|
|
58
64
|
/**
|
|
59
|
-
*
|
|
65
|
+
* do not replenish, only consume
|
|
66
|
+
*
|
|
67
|
+
* ## intent
|
|
68
|
+
* adding this for revocation, though we have added deleteAll for this now.
|
|
69
|
+
* Leaving it in.
|
|
60
70
|
*/
|
|
61
71
|
consume: KEYSTONE_REPLENISH_STRATEGY_CONSUME,
|
|
62
72
|
/**
|
|
63
|
-
*
|
|
73
|
+
* Deletes ALL challenges in the pool, regardless of how many were used.
|
|
74
|
+
* The "Nuclear Option" for revocation.
|
|
64
75
|
*/
|
|
65
|
-
|
|
76
|
+
deleteAll: KEYSTONE_REPLENISH_STRATEGY_DELETE_ALL,
|
|
66
77
|
} satisfies { [key: string]: KeystoneReplenishStrategy };
|
|
67
78
|
export const KEYSTONE_REPLENISH_STRATEGY_VALID_VALUES = Object.values(KeystoneReplenishStrategy);
|
|
68
79
|
export function isKeystoneReplenishStrategy(x: any): x is KeystoneReplenishStrategy {
|
|
69
|
-
return KEYSTONE_REPLENISH_STRATEGY_VALID_VALUES.includes(x);
|
|
80
|
+
return typeof x === 'string' && KEYSTONE_REPLENISH_STRATEGY_VALID_VALUES.includes(x as any);
|
|
70
81
|
}
|
|
71
82
|
// #endregion KeystoneReplenishStrategy
|
|
72
83
|
|
|
@@ -78,6 +89,9 @@ export interface KeystonePoolBehavior {
|
|
|
78
89
|
|
|
79
90
|
/**
|
|
80
91
|
* How do we fill the void left by consumed challenges?
|
|
92
|
+
*
|
|
93
|
+
* @see {@link KeystoneReplenishStrategy} individual members for information
|
|
94
|
+
* on each one.
|
|
81
95
|
*/
|
|
82
96
|
replenish: KeystoneReplenishStrategy;
|
|
83
97
|
|
|
@@ -219,11 +233,11 @@ export interface KeystoneChallengePool {
|
|
|
219
233
|
bindingMap: { [hexChar: string]: string[] };
|
|
220
234
|
|
|
221
235
|
/**
|
|
222
|
-
* If true, this pool's secrets are NOT derived from the Keystone's
|
|
236
|
+
* If true, this pool's secrets are NOT derived from the Keystone's
|
|
223
237
|
* primary Master Secret. They are held by an external entity.
|
|
224
|
-
*
|
|
238
|
+
*
|
|
225
239
|
* ## intent
|
|
226
|
-
*
|
|
240
|
+
*
|
|
227
241
|
* The driving use case for this is signing in with a server "super node"
|
|
228
242
|
* and giving that node the ability to sign on behalf of the user. This is a
|
|
229
243
|
* common pattern in SSO-type workflows.
|
|
@@ -233,9 +247,9 @@ export interface KeystoneChallengePool {
|
|
|
233
247
|
/**
|
|
234
248
|
* Arbitrary metadata for the wallet/user to identify the pool.
|
|
235
249
|
* e.g. { delegate: "PrimaryServer", purpose: "SSO" }
|
|
236
|
-
*
|
|
250
|
+
*
|
|
237
251
|
* ## intent
|
|
238
|
-
*
|
|
252
|
+
*
|
|
239
253
|
* The driving use case for this is signing in with a server "super node"
|
|
240
254
|
* and giving that node the ability to sign on behalf of the user. This is a
|
|
241
255
|
* common pattern in SSO-type workflows.
|
|
@@ -288,7 +302,7 @@ export interface KeystoneRevocationInfo {
|
|
|
288
302
|
// TOP LEVEL IBGIB DATA
|
|
289
303
|
// ===========================================================================
|
|
290
304
|
|
|
291
|
-
export interface
|
|
305
|
+
export interface KeystoneIb_V1 {
|
|
292
306
|
atom: typeof KEYSTONE_ATOM;
|
|
293
307
|
}
|
|
294
308
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { hash } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
|
|
2
|
+
|
|
2
3
|
import { KeystoneStrategy } from '../keystone-strategy.mjs';
|
|
3
4
|
import {
|
|
4
|
-
KeystonePoolConfig_HashV1,
|
|
5
|
-
KeystoneChallenge_HashV1,
|
|
6
|
-
KeystoneSolution_HashV1
|
|
5
|
+
KeystonePoolConfig_HashV1, KeystoneChallenge_HashV1, KeystoneSolution_HashV1
|
|
7
6
|
} from '../../keystone-types.mjs';
|
|
7
|
+
import { kdf_recursiveSaltWrap } from '../../kdf/kdf-helpers.mjs';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* The concrete implementation of the "Salted Wrap" Hash Reveal strategy.
|
|
@@ -26,17 +26,13 @@ export class KeystoneStrategy_HashRevealV1 extends KeystoneStrategy<
|
|
|
26
26
|
const lc = `[${KeystoneStrategy_HashRevealV1.name}.${this.derivePoolSecret.name}]`;
|
|
27
27
|
try {
|
|
28
28
|
const { salt, rounds, algo } = this.config;
|
|
29
|
-
// Map algo string to HashAlgorithm type if needed,
|
|
30
|
-
// assuming config.algo matches the helper's expected inputs.
|
|
31
29
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
return current;
|
|
30
|
+
return await kdf_recursiveSaltWrap({
|
|
31
|
+
masterSecret,
|
|
32
|
+
salt,
|
|
33
|
+
rounds,
|
|
34
|
+
algorithm: algo
|
|
35
|
+
});
|
|
40
36
|
} catch (error) {
|
|
41
37
|
console.error(`${lc} Error deriving pool secret: ${error.message}`);
|
|
42
38
|
throw error;
|