@ibgib/core-gib 0.1.8 → 0.1.10
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/agent-helpers.d.mts +45 -0
- package/dist/agent-helpers.d.mts.map +1 -0
- package/dist/agent-helpers.mjs +36 -0
- package/dist/agent-helpers.mjs.map +1 -0
- package/dist/keystone/keystone-config-builder.respec.d.mts +2 -0
- package/dist/keystone/keystone-config-builder.respec.d.mts.map +1 -0
- package/dist/keystone/keystone-config-builder.respec.mjs +34 -0
- package/dist/keystone/keystone-config-builder.respec.mjs.map +1 -0
- package/dist/keystone/keystone-constants.d.mts +2 -0
- package/dist/keystone/keystone-constants.d.mts.map +1 -1
- package/dist/keystone/keystone-constants.mjs +2 -0
- package/dist/keystone/keystone-constants.mjs.map +1 -1
- package/dist/keystone/keystone-helpers.d.mts +54 -1
- package/dist/keystone/keystone-helpers.d.mts.map +1 -1
- package/dist/keystone/keystone-helpers.mjs +185 -1
- package/dist/keystone/keystone-helpers.mjs.map +1 -1
- package/dist/keystone/keystone-service-v1.d.mts +49 -16
- package/dist/keystone/keystone-service-v1.d.mts.map +1 -1
- package/dist/keystone/keystone-service-v1.mjs +151 -328
- package/dist/keystone/keystone-service-v1.mjs.map +1 -1
- package/dist/keystone/keystone-service-v1.respec.mjs +401 -20
- package/dist/keystone/keystone-service-v1.respec.mjs.map +1 -1
- package/dist/keystone/keystone-types.d.mts +22 -0
- package/dist/keystone/keystone-types.d.mts.map +1 -1
- package/dist/sync/sync-constants.d.mts +17 -0
- package/dist/sync/sync-constants.d.mts.map +1 -0
- package/dist/sync/sync-constants.mjs +16 -0
- package/dist/sync/sync-constants.mjs.map +1 -0
- package/dist/sync/sync-helpers.d.mts +15 -0
- package/dist/sync/sync-helpers.d.mts.map +1 -0
- package/dist/sync/sync-helpers.mjs +46 -0
- package/dist/sync/sync-helpers.mjs.map +1 -0
- package/dist/sync/sync-local-spaces.respec.d.mts +2 -0
- package/dist/sync/sync-local-spaces.respec.d.mts.map +1 -0
- package/dist/sync/sync-local-spaces.respec.mjs +159 -0
- package/dist/sync/sync-local-spaces.respec.mjs.map +1 -0
- package/dist/sync/sync-saga-coordinator.d.mts +118 -0
- package/dist/sync/sync-saga-coordinator.d.mts.map +1 -0
- package/dist/sync/sync-saga-coordinator.mjs +399 -0
- package/dist/sync/sync-saga-coordinator.mjs.map +1 -0
- package/dist/sync/sync-saga-coordinator.respec.d.mts +2 -0
- package/dist/sync/sync-saga-coordinator.respec.d.mts.map +1 -0
- package/dist/sync/sync-saga-coordinator.respec.mjs +40 -0
- package/dist/sync/sync-saga-coordinator.respec.mjs.map +1 -0
- package/dist/sync/sync-types.d.mts +103 -0
- package/dist/sync/sync-types.d.mts.map +1 -0
- package/dist/sync/sync-types.mjs +2 -0
- package/dist/sync/sync-types.mjs.map +1 -0
- package/dist/test/mock-space.d.mts +39 -0
- package/dist/test/mock-space.d.mts.map +1 -0
- package/dist/test/mock-space.mjs +79 -0
- package/dist/test/mock-space.mjs.map +1 -0
- package/dist/witness/space/inner-space/inner-space-v1.respec.mjs +163 -201
- package/dist/witness/space/inner-space/inner-space-v1.respec.mjs.map +1 -1
- package/dist/witness/space/space-helper.d.mts.map +1 -1
- package/dist/witness/space/space-helper.mjs +43 -4
- package/dist/witness/space/space-helper.mjs.map +1 -1
- package/dist/witness/space/space-helper.respec.d.mts +2 -0
- package/dist/witness/space/space-helper.respec.d.mts.map +1 -0
- package/dist/witness/space/space-helper.respec.mjs +30 -0
- package/dist/witness/space/space-helper.respec.mjs.map +1 -0
- package/package.json +2 -2
- package/src/agent-helpers.mts +58 -0
- package/src/keystone/keystone-config-builder.respec.mts +49 -0
- package/src/keystone/keystone-constants.mts +2 -0
- package/src/keystone/keystone-helpers.mts +211 -2
- package/src/keystone/keystone-service-v1.mts +183 -367
- package/src/keystone/keystone-service-v1.respec.mts +484 -21
- package/src/keystone/keystone-types.mts +24 -0
- package/src/sync/sync-constants.mts +24 -0
- package/src/sync/sync-helpers.mts +59 -0
- package/src/sync/sync-local-spaces.respec.mts +200 -0
- package/src/sync/sync-saga-coordinator.mts +477 -0
- package/src/sync/sync-saga-coordinator.respec.mts +52 -0
- package/src/sync/sync-types.mts +120 -0
- package/src/test/mock-space.mts +85 -0
- package/src/witness/space/inner-space/inner-space-v1.respec.mts +181 -228
- package/src/witness/space/space-helper.mts +42 -4
- package/src/witness/space/space-helper.respec.mts +42 -0
- package/tmp.md +11 -0
|
@@ -1,36 +1,20 @@
|
|
|
1
|
-
import { extractErrorMsg,
|
|
1
|
+
import { extractErrorMsg, } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
|
|
2
2
|
import { getIbGibAddr } from '@ibgib/ts-gib/dist/helper.mjs';
|
|
3
|
-
import { mut8 } from '@ibgib/ts-gib/dist/V1/transforms/mut8.mjs';
|
|
4
|
-
import { Factory_V1 } from '@ibgib/ts-gib/dist/V1/factory.mjs';
|
|
5
|
-
import { getGib, getGibInfo } from '@ibgib/ts-gib/dist/V1/transforms/transform-helper.mjs';
|
|
6
|
-
import { TransformResult } from '@ibgib/ts-gib/dist/types.mjs';
|
|
7
|
-
import { validateIbGibIntrinsically } from '@ibgib/ts-gib/dist/V1/validate-helper.mjs';
|
|
8
3
|
|
|
9
4
|
import { GLOBAL_LOG_A_LOT } from '../core-constants.mjs';
|
|
10
5
|
import {
|
|
11
|
-
KeystoneData_V1,
|
|
12
|
-
|
|
13
|
-
KeystonePoolConfig,
|
|
14
|
-
KeystoneClaim,
|
|
15
|
-
KeystoneProof,
|
|
16
|
-
KeystoneChallengePool,
|
|
17
|
-
KeystoneSolution,
|
|
18
|
-
KeystoneReplenishStrategy,
|
|
19
|
-
KeystoneRevocationInfo,
|
|
20
|
-
KEYSTONE_REPLENISH_STRATEGY_VALID_VALUES,
|
|
6
|
+
KeystoneData_V1, KeystoneIbGib_V1, KeystonePoolConfig, KeystoneClaim,
|
|
7
|
+
KeystoneChallengePool, KeystoneReplenishStrategy, KeystoneRevocationInfo,
|
|
21
8
|
} from './keystone-types.mjs';
|
|
22
9
|
import { KeystoneStrategyFactory } from './strategy/keystone-strategy-factory.mjs';
|
|
23
|
-
import {
|
|
10
|
+
import { POOL_ID_REVOKE, KEYSTONE_VERB_REVOKE, KEYSTONE_VERB_MANAGE } from './keystone-constants.mjs';
|
|
24
11
|
import {
|
|
25
|
-
addToBindingMap,
|
|
26
|
-
|
|
27
|
-
validateKeystoneTransition,
|
|
28
|
-
verifyProofAgainstPool,
|
|
12
|
+
addToBindingMap, createKeystoneIbGibImpl, evolvePersistAndRegisterKeystone,
|
|
13
|
+
generateOpaqueChallengeId, resolveTargetPool, selectChallengeIds,
|
|
14
|
+
solveAndReplenish, validateKeystoneTransition,
|
|
29
15
|
} from './keystone-helpers.mjs';
|
|
30
|
-
import { getDependencyGraph } from '../common/other/graph-helper.mjs';
|
|
31
16
|
import { IbGibSpaceAny } from '../witness/space/space-base-v1.mjs';
|
|
32
17
|
import { MetaspaceService } from '../witness/space/metaspace/metaspace-types.mjs';
|
|
33
|
-
import { IbGibSpaceOptionsCmd } from '../witness/space/space-types.mjs';
|
|
34
18
|
|
|
35
19
|
const logalot = GLOBAL_LOG_A_LOT || true;
|
|
36
20
|
|
|
@@ -72,7 +56,7 @@ export class KeystoneService_V1 {
|
|
|
72
56
|
const timestamp = Date.now().toString();
|
|
73
57
|
|
|
74
58
|
for (let i = 0; i < targetSize; i++) {
|
|
75
|
-
const challengeId = await
|
|
59
|
+
const challengeId = await generateOpaqueChallengeId({
|
|
76
60
|
salt: config.salt, timestamp, index: i
|
|
77
61
|
});
|
|
78
62
|
|
|
@@ -94,8 +78,10 @@ export class KeystoneService_V1 {
|
|
|
94
78
|
});
|
|
95
79
|
}
|
|
96
80
|
|
|
81
|
+
if (challengePools.length === 0) { throw new Error(`No challenge pools created. (E: 38e538530996940e1f16a8b199995825)`); }
|
|
82
|
+
|
|
97
83
|
const data: KeystoneData_V1 = { challengePools, proofs: [] };
|
|
98
|
-
const keystoneIbGib = await
|
|
84
|
+
const keystoneIbGib = await createKeystoneIbGibImpl({ data, metaspace, space });
|
|
99
85
|
return keystoneIbGib;
|
|
100
86
|
} catch (error) {
|
|
101
87
|
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
@@ -106,23 +92,41 @@ export class KeystoneService_V1 {
|
|
|
106
92
|
}
|
|
107
93
|
|
|
108
94
|
/**
|
|
109
|
-
* Signs a claim
|
|
110
|
-
*
|
|
95
|
+
* Signs a claim by solving challenges from a specific pool and evolving the Keystone timeline.
|
|
96
|
+
*
|
|
97
|
+
* Uses a hybrid selection strategy: Mandatory IDs (Alice) + Sequential (FIFO) + Random (Stochastic).
|
|
98
|
+
*
|
|
99
|
+
* Supports Delegation via `poolFilter` to find specific foreign pools.
|
|
111
100
|
*/
|
|
112
101
|
async sign({
|
|
113
102
|
latestKeystone,
|
|
114
103
|
masterSecret,
|
|
115
104
|
claim,
|
|
116
105
|
poolId,
|
|
106
|
+
poolFilter,
|
|
117
107
|
requiredChallengeIds = [],
|
|
118
108
|
frameDetails,
|
|
119
109
|
metaspace,
|
|
120
110
|
space,
|
|
121
111
|
}: {
|
|
122
112
|
latestKeystone: KeystoneIbGib_V1;
|
|
113
|
+
/**
|
|
114
|
+
* The secret used to solve the challenges.
|
|
115
|
+
* If signing with a native pool, this is the User's Master Secret.
|
|
116
|
+
* If signing with a foreign/delegated pool, this is the Delegate's Secret.
|
|
117
|
+
*/
|
|
123
118
|
masterSecret: string;
|
|
124
119
|
claim: Partial<KeystoneClaim>;
|
|
120
|
+
/**
|
|
121
|
+
* Explicit ID of the pool to use.
|
|
122
|
+
*/
|
|
125
123
|
poolId?: string;
|
|
124
|
+
/**
|
|
125
|
+
* Optional predicate to find a pool.
|
|
126
|
+
* Useful for finding delegates via metadata without knowing the exact ID.
|
|
127
|
+
* e.g. (p) => p.metadata?.delegate === 'Bob'
|
|
128
|
+
*/
|
|
129
|
+
poolFilter?: (pool: KeystoneChallengePool) => boolean;
|
|
126
130
|
requiredChallengeIds?: string[];
|
|
127
131
|
frameDetails?: any;
|
|
128
132
|
metaspace: MetaspaceService;
|
|
@@ -130,117 +134,58 @@ export class KeystoneService_V1 {
|
|
|
130
134
|
}): Promise<KeystoneIbGib_V1> {
|
|
131
135
|
const lc = `${this.lc}[${this.sign.name}]`;
|
|
132
136
|
try {
|
|
133
|
-
if (logalot) { console.log(`${lc} starting
|
|
137
|
+
if (logalot) { console.log(`${lc} starting...`); }
|
|
134
138
|
|
|
135
139
|
const prevData = latestKeystone.data!;
|
|
136
|
-
if (prevData.revocationInfo) { throw new Error(`keystone has been revoked (latestKeystone.data.revocationInfo is truthy). (E: 4f2198c39116d15c48ba191940316825)`); }
|
|
137
|
-
|
|
138
|
-
let pool: KeystoneChallengePool | undefined;
|
|
139
|
-
const poolsToSearch = prevData.challengePools;
|
|
140
|
-
if (logalot) { console.log(`${lc} Searching pools: ${poolsToSearch.map(p => p.id).join(', ')} for target: ${poolId || 'auto'}`); }
|
|
141
|
-
|
|
142
|
-
// ---------------------------------------------------------------
|
|
143
|
-
// 0. POOL RESOLUTION (Deterministic Mapping)
|
|
144
|
-
// ---------------------------------------------------------------
|
|
145
|
-
if (poolId) {
|
|
146
|
-
// Explicit selection
|
|
147
|
-
pool = poolsToSearch.find(p => p.id === poolId);
|
|
148
|
-
if (!pool) { throw new Error(`Pool not found: ${poolId} (E: genuuid)`); }
|
|
149
|
-
|
|
150
|
-
// Enforce Policy on Explicit Selection
|
|
151
|
-
// FIX: Check length > 0. If empty, it's a default pool (allow all).
|
|
152
|
-
if (pool.config.allowedVerbs && pool.config.allowedVerbs.length > 0 && claim.verb) {
|
|
153
|
-
if (!pool.config.allowedVerbs.includes(claim.verb)) {
|
|
154
|
-
throw new Error(`Pool ${poolId} is not authorized for verb: ${claim.verb} (E: genuuid)`);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
} else {
|
|
158
|
-
// Automatic Resolution based on Verb
|
|
159
|
-
if (!claim.verb) { throw new Error(`Cannot auto-resolve pool without a verb in the claim. (E: genuuid)`); }
|
|
160
|
-
|
|
161
|
-
// 1. Look for Specific Match
|
|
162
|
-
pool = poolsToSearch.find(p =>
|
|
163
|
-
p.config.allowedVerbs && p.config.allowedVerbs.includes(claim.verb!)
|
|
164
|
-
);
|
|
165
|
-
|
|
166
|
-
// 2. Look for General/Default (No restrictions)
|
|
167
|
-
if (!pool) {
|
|
168
|
-
pool = poolsToSearch.find(p =>
|
|
169
|
-
!p.config.allowedVerbs || p.config.allowedVerbs.length === 0
|
|
170
|
-
);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
if (!pool) { throw new Error(`No suitable pool found for verb: ${claim.verb} (E: genuuid)`); }
|
|
174
|
-
}
|
|
175
140
|
|
|
141
|
+
if (prevData.revocationInfo) { throw new Error(`Keystone has been revoked. Cannot sign. (E: 4f2198c39116d15c48ba191940316825)`); }
|
|
176
142
|
|
|
177
|
-
// 1.
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
143
|
+
// 1. Identify Authority (Resolve Pool)
|
|
144
|
+
const pool = resolveTargetPool({
|
|
145
|
+
pools: prevData.challengePools,
|
|
146
|
+
poolId,
|
|
147
|
+
poolFilter,
|
|
148
|
+
verb: claim.verb
|
|
182
149
|
});
|
|
183
150
|
|
|
184
|
-
|
|
185
|
-
const randomCount = pool.config.behavior.selectRandomly;
|
|
186
|
-
const randomIds: string[] = [];
|
|
187
|
-
|
|
188
|
-
if (logalot) {
|
|
189
|
-
console.log(`${lc} Pool Info: id=${pool.id} invalid=${!pool} size=${Object.keys(pool.challenges || {}).length}`);
|
|
190
|
-
console.log(`${lc} Behavior: seq=${pool.config.behavior.selectSequentially} rand=${randomCount} binding=${pool.config.behavior.targetBindingChars}`);
|
|
191
|
-
console.log(`${lc} Requirements: mandatory=${mandatoryIds.size} available=${availableIds.length}`);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
if (randomCount > 0) {
|
|
195
|
-
if (availableIds.length < randomCount) {
|
|
196
|
-
throw new Error(`Insufficient challenges for random requirement. Need ${randomCount}, have ${availableIds.length}`);
|
|
197
|
-
}
|
|
198
|
-
// Shuffle & Pick
|
|
199
|
-
const shuffled = availableIds.sort(() => 0.5 - Math.random());
|
|
200
|
-
randomIds.push(...shuffled.slice(0, randomCount));
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// 3. Combine & Solve
|
|
204
|
-
const finalSelectedIds = [...mandatoryIds, ...randomIds];
|
|
205
|
-
|
|
206
|
-
const strategy = KeystoneStrategyFactory.create({ config: pool.config });
|
|
207
|
-
const secretToUse = masterSecret;
|
|
208
|
-
const poolSecret = await strategy.derivePoolSecret({ masterSecret: secretToUse });
|
|
209
|
-
const solutions: KeystoneSolution[] = [];
|
|
151
|
+
if (logalot) { console.log(`${lc} Selected pool: ${pool.id} (size: ${Object.keys(pool.challenges).length}) (I: genuuid)`); }
|
|
210
152
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// 4. Replenish
|
|
219
|
-
const nextPools = await this.applyReplenishmentStrategy({
|
|
220
|
-
prevPools: poolsToSearch,
|
|
221
|
-
targetPoolId: pool.id,
|
|
222
|
-
consumedIds: finalSelectedIds,
|
|
223
|
-
masterSecret: secretToUse,
|
|
224
|
-
strategy,
|
|
225
|
-
config: pool.config
|
|
153
|
+
// 2. Calculate Costs (Select IDs)
|
|
154
|
+
const idsToSolve = selectChallengeIds({
|
|
155
|
+
pool,
|
|
156
|
+
targetAddr: claim.target,
|
|
157
|
+
requiredChallengeIds
|
|
226
158
|
});
|
|
227
159
|
|
|
228
|
-
//
|
|
229
|
-
|
|
160
|
+
// 3. Pay the Cost (Solve & Replenish)
|
|
161
|
+
// This helper handles the Strategy creation, Secret derivation, Solving,
|
|
162
|
+
// and the calculation of the Next state for ALL pools.
|
|
163
|
+
const { proof, nextPools } = await solveAndReplenish({
|
|
164
|
+
targetPoolId: pool.id,
|
|
165
|
+
prevPools: prevData.challengePools,
|
|
166
|
+
masterSecret,
|
|
167
|
+
challengeIds: idsToSolve,
|
|
230
168
|
claim,
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
};
|
|
169
|
+
requiredChallengeIds
|
|
170
|
+
});
|
|
234
171
|
|
|
172
|
+
// 4. Construct New Data
|
|
235
173
|
const newData: KeystoneData_V1 = {
|
|
236
174
|
challengePools: nextPools,
|
|
237
175
|
proofs: [proof],
|
|
238
176
|
frameDetails,
|
|
177
|
+
// Revocation info is undefined for a standard sign operation
|
|
239
178
|
};
|
|
240
179
|
|
|
241
|
-
|
|
242
|
-
|
|
180
|
+
// 5. Commit (Evolve, Persist, Register)
|
|
181
|
+
const resKeystone = await evolvePersistAndRegisterKeystone({
|
|
182
|
+
prevIbGib: latestKeystone,
|
|
183
|
+
newData,
|
|
184
|
+
metaspace,
|
|
185
|
+
space
|
|
186
|
+
});
|
|
243
187
|
|
|
188
|
+
return resKeystone;
|
|
244
189
|
} catch (error) {
|
|
245
190
|
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
246
191
|
throw error;
|
|
@@ -278,8 +223,9 @@ export class KeystoneService_V1 {
|
|
|
278
223
|
*
|
|
279
224
|
* Logic:
|
|
280
225
|
* 1. Locates the 'revoke' pool.
|
|
281
|
-
* 2.
|
|
282
|
-
* 3.
|
|
226
|
+
* 2. Solves required challenges to prove ownership.
|
|
227
|
+
* 3. Wipes the pool (via 'scorched-earth' strategy in solveAndReplenish).
|
|
228
|
+
* 4. Sets the revocationInfo on the new frame.
|
|
283
229
|
*/
|
|
284
230
|
async revoke({
|
|
285
231
|
latestKeystone,
|
|
@@ -294,72 +240,56 @@ export class KeystoneService_V1 {
|
|
|
294
240
|
reason?: string;
|
|
295
241
|
frameDetails?: any;
|
|
296
242
|
metaspace: MetaspaceService;
|
|
297
|
-
space
|
|
243
|
+
space: IbGibSpaceAny;
|
|
298
244
|
}): Promise<KeystoneIbGib_V1> {
|
|
299
245
|
const lc = `${this.lc}[${this.revoke.name}]`;
|
|
300
246
|
try {
|
|
247
|
+
if (logalot) { console.log(`${lc} starting...`); }
|
|
248
|
+
|
|
301
249
|
const prevData = latestKeystone.data!;
|
|
302
|
-
space ??= await metaspace.getLocalUserSpace({ lock: false });
|
|
303
|
-
if (!space) { throw new Error(`(UNEXPECTED) space falsy and couldn't get default local user space from the metaspace? (E: 73c8bfc0e7383a540ea1d6b14b020125)`); }
|
|
304
250
|
|
|
305
|
-
// 1.
|
|
306
|
-
const pool =
|
|
307
|
-
|
|
251
|
+
// 1. Identify Authority (Resolve Revoke Pool)
|
|
252
|
+
const pool = resolveTargetPool({
|
|
253
|
+
pools: prevData.challengePools,
|
|
254
|
+
poolId: POOL_ID_REVOKE // Explicitly require the special revoke pool
|
|
255
|
+
});
|
|
308
256
|
|
|
309
|
-
// 2.
|
|
257
|
+
// 2. Construct Claim
|
|
310
258
|
const claim: Partial<KeystoneClaim> = {
|
|
311
259
|
verb: KEYSTONE_VERB_REVOKE,
|
|
312
260
|
target: getIbGibAddr({ ibGib: latestKeystone })
|
|
313
261
|
};
|
|
314
262
|
|
|
315
|
-
|
|
263
|
+
// 3. Calculate Costs
|
|
264
|
+
const idsToSolve = selectChallengeIds({
|
|
316
265
|
pool,
|
|
317
|
-
|
|
318
|
-
|
|
266
|
+
targetAddr: claim.target,
|
|
267
|
+
requiredChallengeIds: []
|
|
319
268
|
});
|
|
320
269
|
|
|
321
|
-
|
|
322
|
-
const randomCount = pool.config.behavior.selectRandomly;
|
|
323
|
-
const randomIds: string[] = [];
|
|
324
|
-
if (randomCount > 0) {
|
|
325
|
-
if (availableIds.length < randomCount) { throw new Error(`Insufficient challenges. availableIds.length (${availableIds.length}) is less than required random count (${randomCount}) (E: b2e3570ab998dfdbab5fbdda1e43d825)`); }
|
|
326
|
-
const shuffled = availableIds.sort(() => 0.5 - Math.random());
|
|
327
|
-
randomIds.push(...shuffled.slice(0, randomCount));
|
|
328
|
-
}
|
|
270
|
+
if (idsToSolve.length === 0) { throw new Error(`Revocation policy selected 0 challenges? Check config for pool ${pool.id}. Revocation requires proof. (E: 97e5a8356d241ae7b882db791cb1f825)`); }
|
|
329
271
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
const strategy = KeystoneStrategyFactory.create({ config: pool.config });
|
|
335
|
-
const poolSecret = await strategy.derivePoolSecret({ masterSecret });
|
|
336
|
-
const solutions: KeystoneSolution[] = [];
|
|
337
|
-
|
|
338
|
-
for (const id of selectedIds) {
|
|
339
|
-
const solution = await strategy.generateSolution({
|
|
340
|
-
poolSecret, poolId: pool.id, challengeId: id,
|
|
341
|
-
});
|
|
342
|
-
solutions.push(solution);
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
// 4. Replenish (Scorched Earth)
|
|
346
|
-
const nextPools = await this.applyReplenishmentStrategy({
|
|
347
|
-
prevPools: prevData.challengePools,
|
|
272
|
+
// 4. Pay the Cost & Scorched Earth
|
|
273
|
+
// The revoke pool config should have 'replenish: scorched-earth',
|
|
274
|
+
// causing solveAndReplenish to return an empty pool in nextPools.
|
|
275
|
+
const { proof, nextPools } = await solveAndReplenish({
|
|
348
276
|
targetPoolId: pool.id,
|
|
349
|
-
|
|
277
|
+
prevPools: prevData.challengePools,
|
|
350
278
|
masterSecret,
|
|
351
|
-
|
|
352
|
-
config: pool.config
|
|
353
|
-
});
|
|
354
|
-
|
|
355
|
-
// 5. Construct Proof
|
|
356
|
-
const proof: KeystoneProof = {
|
|
279
|
+
challengeIds: idsToSolve,
|
|
357
280
|
claim,
|
|
358
|
-
|
|
359
|
-
};
|
|
281
|
+
requiredChallengeIds: []
|
|
282
|
+
});
|
|
283
|
+
// warn if nextPools contains pool.id that isn't empty (we were
|
|
284
|
+
// supposed to do "scorched earth" which empties the pool)
|
|
285
|
+
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.scorchedEarth}? (W: 300c28bc8b98fc3e3c0b0d988344f825)`);
|
|
287
|
+
}
|
|
360
288
|
|
|
289
|
+
// 5. Construct Revocation Info
|
|
361
290
|
const revocationInfo: KeystoneRevocationInfo = { reason, proof };
|
|
362
291
|
|
|
292
|
+
// 6. Construct New Data
|
|
363
293
|
const newData: KeystoneData_V1 = {
|
|
364
294
|
challengePools: nextPools,
|
|
365
295
|
proofs: [proof],
|
|
@@ -367,13 +297,15 @@ export class KeystoneService_V1 {
|
|
|
367
297
|
frameDetails
|
|
368
298
|
};
|
|
369
299
|
|
|
370
|
-
|
|
300
|
+
// 7. Commit
|
|
301
|
+
const newKeystone = await evolvePersistAndRegisterKeystone({
|
|
371
302
|
prevIbGib: latestKeystone,
|
|
372
303
|
newData,
|
|
373
304
|
metaspace,
|
|
374
|
-
space
|
|
305
|
+
space
|
|
375
306
|
});
|
|
376
307
|
|
|
308
|
+
return newKeystone;
|
|
377
309
|
} catch (error) {
|
|
378
310
|
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
379
311
|
throw error;
|
|
@@ -383,224 +315,108 @@ export class KeystoneService_V1 {
|
|
|
383
315
|
}
|
|
384
316
|
|
|
385
317
|
/**
|
|
386
|
-
*
|
|
387
|
-
*
|
|
318
|
+
* Structural evolution: Adds new challenge pools to the keystone.
|
|
319
|
+
*
|
|
320
|
+
* Use Case: Adding a delegate (Server) for SSO, adding a recovery key,
|
|
321
|
+
* or rotating to a new set of pools.
|
|
322
|
+
*
|
|
323
|
+
* Requires the Master Secret to authorize the change via a pool containing
|
|
324
|
+
* the 'manage' verb.
|
|
388
325
|
*/
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
timestamp,
|
|
392
|
-
index
|
|
393
|
-
}: {
|
|
394
|
-
salt: string,
|
|
395
|
-
timestamp: string,
|
|
396
|
-
index: number
|
|
397
|
-
}): Promise<string> {
|
|
398
|
-
// Use full SHA-256 hash, or slice it?
|
|
399
|
-
// User suggested substring is fine for pool uniqueness.
|
|
400
|
-
// Let's use first 16 chars of hex (64 bits) for brevity + safety.
|
|
401
|
-
const raw = await hash({ s: `${salt}${timestamp}${index}` });
|
|
402
|
-
return raw.substring(0, 16);
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
private async applyReplenishmentStrategy({
|
|
406
|
-
prevPools,
|
|
407
|
-
targetPoolId,
|
|
408
|
-
consumedIds,
|
|
326
|
+
async addPools({
|
|
327
|
+
latestKeystone,
|
|
409
328
|
masterSecret,
|
|
410
|
-
|
|
411
|
-
config
|
|
412
|
-
}: {
|
|
413
|
-
prevPools: KeystoneChallengePool[];
|
|
414
|
-
targetPoolId: string;
|
|
415
|
-
consumedIds: string[];
|
|
416
|
-
masterSecret: string;
|
|
417
|
-
strategy: any;
|
|
418
|
-
config: KeystonePoolConfig;
|
|
419
|
-
}): Promise<KeystoneChallengePool[]> {
|
|
420
|
-
const newPools = JSON.parse(JSON.stringify(prevPools));
|
|
421
|
-
if (logalot) { console.log(`[applyReplenishmentStrategy] Cloned pools. Ref check: ${prevPools === newPools} (should be false)`); }
|
|
422
|
-
const targetIdx = newPools.findIndex((p: any) => p.id === targetPoolId);
|
|
423
|
-
if (targetIdx === -1) { throw new Error(`Target pool ${targetPoolId} not found in keytstone data. (E: 75200388d22744838634524233772545)`); }
|
|
424
|
-
const pool = newPools[targetIdx];
|
|
425
|
-
|
|
426
|
-
const poolSecret = await strategy.derivePoolSecret({ masterSecret });
|
|
427
|
-
const timestamp = Date.now().toString();
|
|
428
|
-
|
|
429
|
-
const strategyType = config.behavior.replenish;
|
|
430
|
-
|
|
431
|
-
// Clean up Binding Map for consumed IDs
|
|
432
|
-
consumedIds.forEach(id => {
|
|
433
|
-
if (pool.bindingMap) { removeFromBindingMap(pool.bindingMap, id); }
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
if (strategyType === KeystoneReplenishStrategy.topUp) {
|
|
437
|
-
// Remove consumed
|
|
438
|
-
consumedIds.forEach(id => delete pool.challenges[id]);
|
|
439
|
-
|
|
440
|
-
// Add New
|
|
441
|
-
for (let i = 0; i < consumedIds.length; i++) {
|
|
442
|
-
const newId = await this.generateOpaqueChallengeId({
|
|
443
|
-
salt: config.salt, timestamp, index: i
|
|
444
|
-
});
|
|
445
|
-
|
|
446
|
-
const solution = await strategy.generateSolution({
|
|
447
|
-
poolSecret, poolId: pool.id, challengeId: newId
|
|
448
|
-
});
|
|
449
|
-
pool.challenges[newId] = await strategy.generateChallenge({ solution });
|
|
450
|
-
|
|
451
|
-
// Update Binding Map
|
|
452
|
-
if (!pool.bindingMap) { pool.bindingMap = {}; }
|
|
453
|
-
addToBindingMap(pool.bindingMap, newId);
|
|
454
|
-
}
|
|
455
|
-
} else if (strategyType === KeystoneReplenishStrategy.replaceAll) {
|
|
456
|
-
pool.challenges = {};
|
|
457
|
-
pool.bindingMap = {};
|
|
458
|
-
|
|
459
|
-
for (let i = 0; i < config.behavior.size; i++) {
|
|
460
|
-
const newId = await this.generateOpaqueChallengeId({
|
|
461
|
-
salt: config.salt, timestamp, index: i
|
|
462
|
-
});
|
|
463
|
-
const solution = await strategy.generateSolution({
|
|
464
|
-
poolSecret, poolId: pool.id, challengeId: newId
|
|
465
|
-
});
|
|
466
|
-
pool.challenges[newId] = await strategy.generateChallenge({ solution });
|
|
467
|
-
addToBindingMap(pool.bindingMap, newId);
|
|
468
|
-
}
|
|
469
|
-
} else if (strategyType === KeystoneReplenishStrategy.consume) {
|
|
470
|
-
consumedIds.forEach(id => delete pool.challenges[id]);
|
|
471
|
-
} else if (strategyType === KeystoneReplenishStrategy.scorchedEarth) {
|
|
472
|
-
pool.challenges = {};
|
|
473
|
-
pool.bindingMap = {};
|
|
474
|
-
} else {
|
|
475
|
-
throw new Error(`Unknown replenish strategy: ${strategyType}. Valid list: ${pretty(KEYSTONE_REPLENISH_STRATEGY_VALID_VALUES)} (E: 0acf56f1e1486240080e11e8046d0825)`);
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
return newPools;
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
/**
|
|
482
|
-
* Creates a new keystone ibgib that has no dna and no past.
|
|
483
|
-
*/
|
|
484
|
-
private async createKeystoneIbGibImpl({
|
|
485
|
-
data,
|
|
329
|
+
newPools,
|
|
486
330
|
metaspace,
|
|
487
331
|
space,
|
|
488
332
|
}: {
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
333
|
+
latestKeystone: KeystoneIbGib_V1;
|
|
334
|
+
/**
|
|
335
|
+
* Alice's Master Secret.
|
|
336
|
+
* Required to solve challenges from the Admin/Manage pool to authorize this change.
|
|
337
|
+
*/
|
|
338
|
+
masterSecret: string;
|
|
339
|
+
/**
|
|
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
|
|
343
|
+
* using Bob's challenges + Her config restrictions + isForeign=true.
|
|
344
|
+
*/
|
|
345
|
+
newPools: KeystoneChallengePool[];
|
|
346
|
+
metaspace: MetaspaceService;
|
|
347
|
+
space: IbGibSpaceAny;
|
|
492
348
|
}): Promise<KeystoneIbGib_V1> {
|
|
493
|
-
const lc = `${this.lc}[${this.
|
|
349
|
+
const lc = `${this.lc}[${this.addPools.name}]`;
|
|
494
350
|
try {
|
|
495
|
-
if (logalot) { console.log(`${lc} starting
|
|
496
|
-
|
|
497
|
-
space ??= await metaspace.getLocalUserSpace({ lock: false });
|
|
498
|
-
if (!space) { throw new Error(`(UNEXPECTED) space was falsy and we couldn't get default local user space from metaspace? (E: 9a6498cf16a8801f19ec376749742225)`); }
|
|
499
|
-
|
|
500
|
-
// create the actual keystoneIbGib
|
|
501
|
-
const resFirstGen = await Factory_V1.firstGen({
|
|
502
|
-
parentIbGib: Factory_V1.primitive({ ib: KEYSTONE_ATOM }),
|
|
503
|
-
ib: await getKeystoneIb({ keystoneData: data }),
|
|
504
|
-
data,
|
|
505
|
-
dna: false,
|
|
506
|
-
nCounter: true,
|
|
507
|
-
tjp: {
|
|
508
|
-
timestamp: true,
|
|
509
|
-
uuid: true,
|
|
510
|
-
},
|
|
511
|
-
}) as TransformResult<KeystoneIbGib_V1>;
|
|
512
|
-
const keystoneIbGib = resFirstGen.newIbGib;
|
|
513
|
-
|
|
514
|
-
if (!keystoneIbGib.data) { throw new Error(`(UNEXPECTED) keystoneIbGib.data falsy? We expect the data to be populated with real keystone data. (E: 38a358facdb89d16d81d48c8520d3d25)`); }
|
|
515
|
-
if (!keystoneIbGib.rel8ns) { throw new Error(`(UNEXPECTED) keystoneIbGib.rel8ns falsy? we expect the rel8ns to have ancestor and past. (E: 20cb7723dc33ae1ef808fe76d1bf4b25)`); }
|
|
516
|
-
if (!keystoneIbGib.rel8ns.past || keystoneIbGib.rel8ns.past.length === 0) {
|
|
517
|
-
throw new Error(`(UNEXPECTED) keystoneIbGib.rel8ns.past falsy or empty? we expect the firstGen call to generate an interstitial ibgib that we will splice out. (E: 0fd8388d045ab9f37834c27d67e78825)`);
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
// reset n
|
|
521
|
-
keystoneIbGib.data.n = 0;
|
|
522
|
-
// reset tjp
|
|
523
|
-
keystoneIbGib.data.isTjp = true;
|
|
524
|
-
delete keystoneIbGib.rel8ns.tjp;
|
|
525
|
-
// reset past
|
|
526
|
-
delete keystoneIbGib.rel8ns.past;
|
|
351
|
+
if (logalot) { console.log(`${lc} starting...`); }
|
|
527
352
|
|
|
528
|
-
|
|
529
|
-
|
|
353
|
+
if (!latestKeystone.data) { throw new Error(`(UNEXPECTED) latestKeystone.data falsy? (E: 7334c8faed128166a999d428c7805b25)`); }
|
|
354
|
+
const prevData = latestKeystone.data;
|
|
530
355
|
|
|
531
|
-
|
|
532
|
-
await metaspace.put({ ibGib: keystoneIbGib, space, });
|
|
533
|
-
await metaspace.registerNewIbGib({ ibGib: keystoneIbGib, space, });
|
|
356
|
+
if (prevData.revocationInfo) { throw new Error(`Keystone has been revoked. Cannot add pools. (E: 8599f8f51c78d722252ddb2894fdbe25)`); }
|
|
534
357
|
|
|
535
|
-
|
|
536
|
-
} catch (error) {
|
|
537
|
-
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
538
|
-
throw error;
|
|
539
|
-
} finally {
|
|
540
|
-
if (logalot) { console.log(`${lc} complete.`); }
|
|
541
|
-
}
|
|
542
|
-
}
|
|
358
|
+
if (newPools.length === 0) { throw new Error(`No new pools provided to add. (E: 6599f8f51c78d722252ddb2894fdbe25)`); }
|
|
543
359
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
}: {
|
|
550
|
-
prevIbGib: KeystoneIbGib_V1,
|
|
551
|
-
newData: KeystoneData_V1
|
|
552
|
-
metaspace: MetaspaceService,
|
|
553
|
-
space: IbGibSpaceAny,
|
|
554
|
-
}): Promise<KeystoneIbGib_V1> {
|
|
555
|
-
const lc = `${this.lc}[${this.evolveKeystoneIbGibImpl.name}]`;
|
|
556
|
-
try {
|
|
557
|
-
if (logalot) { console.log(`${lc} starting... (I: 8b10e8920f08b7842803665834cf8925)`); }
|
|
558
|
-
|
|
559
|
-
if (!prevIbGib.data) { throw new Error(`(UNEXPECTED) prevIbGib.data falsy? (E: 5e84875bf992c585b979e6c8ed5bf225)`); }
|
|
560
|
-
if (prevIbGib.data.revocationInfo) { throw new Error(`Keystone has already been revoked (prevIbGib.data.revocationInfo truthy), so we cannot evolve the keystone. Keystone addr: ${getIbGibAddr({ ibGib: prevIbGib })} (E: 45d7f846556829de6b2a701838c3f825)`); }
|
|
561
|
-
|
|
562
|
-
const prevData = prevIbGib.data;
|
|
563
|
-
/**
|
|
564
|
-
* we want to completely replace these keys, so we will remove them
|
|
565
|
-
* from the data. This occurs first in the underlying mut8
|
|
566
|
-
* transform.
|
|
567
|
-
* @see {@link mut8}
|
|
568
|
-
*/
|
|
569
|
-
let dataToRemove: Partial<KeystoneData_V1> | undefined = {}
|
|
570
|
-
if (prevData.proofs) { dataToRemove.proofs = []; }
|
|
571
|
-
if (prevData.challengePools) { dataToRemove.challengePools = []; }
|
|
572
|
-
if (prevData.frameDetails) { dataToRemove.frameDetails = {}; }
|
|
573
|
-
if (Object.keys(dataToRemove).length === 0) { dataToRemove = undefined; }
|
|
574
|
-
|
|
575
|
-
const resMut8 = await mut8({
|
|
576
|
-
src: prevIbGib,
|
|
577
|
-
dataToRemove,
|
|
578
|
-
dataToAddOrPatch: newData,
|
|
579
|
-
// dna: false, // explicitly set to false just to show
|
|
580
|
-
nCounter: true,
|
|
360
|
+
// 1. Identify Authority (Resolve Admin Pool)
|
|
361
|
+
// We need a pool that allows the 'manage' verb.
|
|
362
|
+
const adminPool = resolveTargetPool({
|
|
363
|
+
pools: prevData.challengePools,
|
|
364
|
+
verb: KEYSTONE_VERB_MANAGE,
|
|
581
365
|
});
|
|
582
366
|
|
|
583
|
-
if (
|
|
584
|
-
if (!!resMut8.dnas) { throw new Error(`(UNEXPECTED) resMut8.dnas truthy? We do not want dnas with keystones. (E: 49470513d018f97d28024f4e82da3b25)`); }
|
|
367
|
+
if (logalot) { console.log(`${lc} Authorized via pool: ${adminPool.id}`); }
|
|
585
368
|
|
|
369
|
+
// 2. Construct the Management Claim
|
|
370
|
+
const target = getIbGibAddr({ ibGib: latestKeystone });
|
|
371
|
+
const claim: Partial<KeystoneClaim> = {
|
|
372
|
+
verb: KEYSTONE_VERB_MANAGE,
|
|
373
|
+
target, // I am managing myself
|
|
374
|
+
// Scope creates a cryptographic commitment to WHICH pools are being added
|
|
375
|
+
scope: JSON.stringify({ add: newPools.map(p => p.id) })
|
|
376
|
+
};
|
|
586
377
|
|
|
587
|
-
|
|
378
|
+
// 3. Calculate Costs
|
|
379
|
+
const idsToSolve = selectChallengeIds({
|
|
380
|
+
pool: adminPool,
|
|
381
|
+
targetAddr: target
|
|
382
|
+
});
|
|
588
383
|
|
|
589
|
-
//
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
384
|
+
// 4. Pay the Cost (Solve & Replenish)
|
|
385
|
+
// This authorizes the change by evolving the admin pool.
|
|
386
|
+
const { proof, nextPools: replenishedExistingPools } = await solveAndReplenish({
|
|
387
|
+
targetPoolId: adminPool.id,
|
|
388
|
+
prevPools: prevData.challengePools,
|
|
389
|
+
masterSecret,
|
|
390
|
+
challengeIds: idsToSolve,
|
|
391
|
+
claim,
|
|
593
392
|
});
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
393
|
+
|
|
394
|
+
// 5. Mutate Structure (Add the new pools)
|
|
395
|
+
// We verify ID uniqueness cheaply here to prevent blatant errors
|
|
396
|
+
const existingIds = new Set(replenishedExistingPools.map(p => p.id));
|
|
397
|
+
for (const newPool of newPools) {
|
|
398
|
+
if (existingIds.has(newPool.id)) {
|
|
399
|
+
throw new Error(`Cannot add pool. ID collision: ${newPool.id} (E: 8a4c2b1d3e5f6a7b8c9d0e1f2a3b4c5d)`);
|
|
400
|
+
}
|
|
597
401
|
}
|
|
598
402
|
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
403
|
+
const finalPools = [...replenishedExistingPools, ...newPools];
|
|
404
|
+
|
|
405
|
+
// 6. Construct New Data
|
|
406
|
+
const newData: KeystoneData_V1 = {
|
|
407
|
+
challengePools: finalPools,
|
|
408
|
+
proofs: [proof], // The proof authorizes the structure change
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
// 7. Commit
|
|
412
|
+
const newKeystone = await evolvePersistAndRegisterKeystone({
|
|
413
|
+
prevIbGib: latestKeystone,
|
|
414
|
+
newData,
|
|
415
|
+
metaspace,
|
|
416
|
+
space
|
|
417
|
+
});
|
|
602
418
|
|
|
603
|
-
return
|
|
419
|
+
return newKeystone;
|
|
604
420
|
} catch (error) {
|
|
605
421
|
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
606
422
|
throw error;
|