@ibgib/core-gib 0.1.6 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/keystone/keystone-config-builder.d.mts +77 -0
  2. package/dist/keystone/keystone-config-builder.d.mts.map +1 -0
  3. package/dist/keystone/keystone-config-builder.mjs +157 -0
  4. package/dist/keystone/keystone-config-builder.mjs.map +1 -0
  5. package/dist/keystone/keystone-config-builder.respec.d.mts +2 -0
  6. package/dist/keystone/keystone-config-builder.respec.d.mts.map +1 -0
  7. package/dist/keystone/keystone-config-builder.respec.mjs +34 -0
  8. package/dist/keystone/keystone-config-builder.respec.mjs.map +1 -0
  9. package/dist/keystone/keystone-constants.d.mts +38 -0
  10. package/dist/keystone/keystone-constants.d.mts.map +1 -0
  11. package/dist/keystone/keystone-constants.mjs +41 -0
  12. package/dist/keystone/keystone-constants.mjs.map +1 -0
  13. package/dist/keystone/keystone-helpers.d.mts +170 -0
  14. package/dist/keystone/keystone-helpers.d.mts.map +1 -0
  15. package/dist/keystone/keystone-helpers.mjs +639 -0
  16. package/dist/keystone/keystone-helpers.mjs.map +1 -0
  17. package/dist/keystone/keystone-service-v1.d.mts +110 -0
  18. package/dist/keystone/keystone-service-v1.d.mts.map +1 -0
  19. package/dist/keystone/keystone-service-v1.mjs +325 -0
  20. package/dist/keystone/keystone-service-v1.mjs.map +1 -0
  21. package/dist/keystone/keystone-service-v1.respec.d.mts +2 -0
  22. package/dist/keystone/keystone-service-v1.respec.d.mts.map +1 -0
  23. package/dist/keystone/keystone-service-v1.respec.mjs +838 -0
  24. package/dist/keystone/keystone-service-v1.respec.mjs.map +1 -0
  25. package/dist/keystone/keystone-types.d.mts +270 -0
  26. package/dist/keystone/keystone-types.d.mts.map +1 -0
  27. package/dist/keystone/keystone-types.mjs +50 -0
  28. package/dist/keystone/keystone-types.mjs.map +1 -0
  29. package/dist/keystone/strategy/hash-reveal-v1/hash-reveal-v1.d.mts +35 -0
  30. package/dist/keystone/strategy/hash-reveal-v1/hash-reveal-v1.d.mts.map +1 -0
  31. package/dist/keystone/strategy/hash-reveal-v1/hash-reveal-v1.mjs +107 -0
  32. package/dist/keystone/strategy/hash-reveal-v1/hash-reveal-v1.mjs.map +1 -0
  33. package/dist/keystone/strategy/keystone-strategy-factory.d.mts +15 -0
  34. package/dist/keystone/strategy/keystone-strategy-factory.d.mts.map +1 -0
  35. package/dist/keystone/strategy/keystone-strategy-factory.mjs +26 -0
  36. package/dist/keystone/strategy/keystone-strategy-factory.mjs.map +1 -0
  37. package/dist/keystone/strategy/keystone-strategy.d.mts +48 -0
  38. package/dist/keystone/strategy/keystone-strategy.d.mts.map +1 -0
  39. package/dist/keystone/strategy/keystone-strategy.mjs +14 -0
  40. package/dist/keystone/strategy/keystone-strategy.mjs.map +1 -0
  41. package/package.json +2 -1
  42. package/src/keystone/README.md +162 -0
  43. package/src/keystone/keystone-config-builder.mts +187 -0
  44. package/src/keystone/keystone-config-builder.respec.mts +49 -0
  45. package/src/keystone/keystone-constants.mts +46 -0
  46. package/src/keystone/keystone-helpers.mts +780 -0
  47. package/src/keystone/keystone-service-v1.mts +427 -0
  48. package/src/keystone/keystone-service-v1.respec.mts +1012 -0
  49. package/src/keystone/keystone-types.mts +339 -0
  50. package/src/keystone/strategy/hash-reveal-v1/hash-reveal-v1.mts +146 -0
  51. package/src/keystone/strategy/keystone-strategy-factory.mts +35 -0
  52. package/src/keystone/strategy/keystone-strategy.mts +71 -0
@@ -0,0 +1,1012 @@
1
+ import {
2
+ respecfully, iReckon, ifWe, firstOfAll, firstOfEach, lastOfAll, lastOfEach, respecfullyDear, ifWeMight
3
+ } from '@ibgib/helper-gib/dist/respec-gib/respec-gib.mjs';
4
+ const maam = `[${import.meta.url}]`, sir = maam;
5
+ import { clone, hash } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
6
+ import { IbGib_V1 } from '@ibgib/ts-gib/dist/V1/types.mjs';
7
+ import { getIbGibAddr } from '@ibgib/ts-gib/dist/helper.mjs';
8
+
9
+ import { GLOBAL_LOG_A_LOT } from '../core-constants.mjs';
10
+ import { KeystoneStrategyFactory } from './strategy/keystone-strategy-factory.mjs';
11
+ import { KeystoneChallengePool, KeystoneClaim, KeystoneIbGib_V1, KeystonePoolConfig_HashV1 } from './keystone-types.mjs';
12
+ import { createRevocationPoolConfig, createStandardPoolConfig } from './keystone-config-builder.mjs';
13
+ import { POOL_ID_DEFAULT, POOL_ID_REVOKE, KEYSTONE_VERB_REVOKE, KEYSTONE_VERB_MANAGE } from './keystone-constants.mjs';
14
+ import { KeystoneService_V1 } from './keystone-service-v1.mjs';
15
+ import { addToBindingMap } from './keystone-helpers.mjs';
16
+
17
+ const logalot = GLOBAL_LOG_A_LOT;
18
+
19
+
20
+ // /**
21
+ // * not sure where to put this, but we probably will want to reuse this in the
22
+ // * future (assuming it works)
23
+ // * @returns metaspace service reference
24
+ // */
25
+ // async function getNewInitializedInMemoryMetaspaceForTesting({
26
+ // defaultSpaceName,
27
+ // }: {
28
+ // defaultSpaceName: string,
29
+ // }): Promise<MetaspaceService> {
30
+ // const lc = `[${getNewInitializedInMemoryMetaspaceForTesting.name}]`;
31
+ // try {
32
+ // if (logalot) { console.log(`${lc} starting... (I: 766d7596addcb73f4820586469233b25)`); }
33
+
34
+ // let metaspace = new Metaspace_Innerspace(/*cacheSvc*/undefined);
35
+ // if (logalot) { console.log(`${lc} creating metaspace complete. initializing... (I: 61b74d62e8832c9fa853e4b8c4c2d825)`); }
36
+ // getGibInfo()
37
+
38
+ // await metaspace.initialize({
39
+ // spaceName: defaultSpaceName,
40
+ // /**
41
+ // * passing in undefined will use the defaults. probably will need to
42
+ // * adjust this for testing purposes, but let's see what happens with
43
+ // * this first.
44
+ // */
45
+ // metaspaceFactory: {
46
+ // fnDtoToSpace: async () => {
47
+ // if (!currentSpace) { currentSpace = new IbGibTestSpace(); }
48
+ // return currentSpace;
49
+ // },
50
+ // fnZeroSpaceFactory: () => {
51
+ // if (!currentZeroSpace) { currentZeroSpace = new IbGibTestSpace(); }
52
+ // return currentZeroSpace;
53
+ // },
54
+ // fnDefaultLocalSpaceFactory: async () => {
55
+ // if (!currentSpace) { currentSpace = new IbGibTestSpace(); }
56
+ // return currentSpace;
57
+ // },
58
+
59
+ // // export type DtoToSpaceFunction = (spaceDto: IbGib_V1) => Promise<IbGibSpaceAny>;
60
+ // // export type ZeroSpaceFactoryFunction = () => IbGibSpaceAny;
61
+ // // export type LocalSpaceFactoryFunction = (opts: CreateLocalSpaceOptions) => Promise<IbGibSpaceAny | undefined>;
62
+ // },
63
+ // getFnAlert: () => { return async ({ title, msg }) => console.log(title, msg) },
64
+ // getFnPrompt: () => {
65
+ // return async ({ title, msg }) => {
66
+ // // if this is needed, we might set up some way for testing
67
+ // // to prepare either a queue of prompts or some kind of map or getter
68
+ // // and put it on the metaspace itself
69
+ // throw new Error(`not implemented (E: c7ef688a02f8cb74487260f9274ac825)`);
70
+ // // promptForText({ title, msg, confirm: false });
71
+ // }
72
+ // },
73
+ // getFnPromptPassword: () => {
74
+ // return async () => {
75
+ // // similar to getFnPrompt, if we need a _different_
76
+ // // password, we might set up some way for testing to prepare
77
+ // // either a queue of passwords or some kind of map or getter
78
+ // // and put it on the metaspace itself
79
+ // return 'password';
80
+ // // promptForSecret({ confirm: true })
81
+ // }
82
+ // },
83
+ // });
84
+ // return metaspace;
85
+ // } catch (error) {
86
+ // console.error(`${lc} ${extractErrorMsg(error)}`);
87
+ // throw error;
88
+ // } finally {
89
+ // if (logalot) { console.log(`${lc} complete.`); }
90
+ // }
91
+ // }
92
+
93
+ /**
94
+ * A simple in-memory map acting as a Space.
95
+ * Pure Storage. No Indexing logic.
96
+ */
97
+ class MockIbGibSpace {
98
+ store = new Map<string, IbGib_V1>();
99
+
100
+ constructor(public name: string = "mock_space") { }
101
+
102
+ async put({ ibGib }: { ibGib: IbGib_V1 }): Promise<void> {
103
+ const addr = getIbGibAddr({ ibGib });
104
+ this.store.set(addr, JSON.parse(JSON.stringify(ibGib))); // Deep copy
105
+ }
106
+
107
+ async get({ addr }: { addr: string }): Promise<IbGib_V1 | null> {
108
+ const data = this.store.get(addr);
109
+ return data ? JSON.parse(JSON.stringify(data)) : null;
110
+ }
111
+
112
+ async witness(arg: any): Promise<any> {
113
+ const cmd = arg.data?.cmd;
114
+ if (cmd === 'get') {
115
+ const addrs = arg.data.ibGibAddrs || [];
116
+ const ibGibs: IbGib_V1[] = [];
117
+ for (const addr of addrs) {
118
+ const ig = await this.get({ addr });
119
+ if (ig) ibGibs.push(ig);
120
+ }
121
+ return { ibGibs };
122
+ }
123
+ return undefined;
124
+ }
125
+ }
126
+
127
+
128
+ /**
129
+ * A partial mock of Metaspace.
130
+ * Handles:
131
+ * 1. Retrieving the local space.
132
+ * 2. Delegating 'put' to the space.
133
+ * 3. 'registerNewIbGib': Tracking the HEAD of a timeline.
134
+ */
135
+ class MockMetaspaceService {
136
+
137
+ /**
138
+ * Map of TJP Gib (Timeline ID) -> Latest IbGib Addr (Head)
139
+ */
140
+ timelineHeads = new Map<string, string>();
141
+
142
+ constructor(public space: MockIbGibSpace) { }
143
+
144
+ async getLocalUserSpace({ lock }: { lock: boolean }): Promise<MockIbGibSpace> {
145
+ return this.space;
146
+ }
147
+
148
+ /**
149
+ * Metaspace often acts as a facade for put, defaulting to local space.
150
+ */
151
+ async put(args: any): Promise<void> {
152
+ const target = args.space || this.space;
153
+ return target.put(args);
154
+ }
155
+
156
+ /**
157
+ * Tracks the latest version of an ibGib timeline.
158
+ */
159
+ async registerNewIbGib(args: { ibGib: IbGib_V1, space?: any }): Promise<void> {
160
+ const { ibGib } = args;
161
+ const targetSpace = args.space || this.space;
162
+
163
+ // 1. Ensure it is stored
164
+ await targetSpace.put({ ibGib });
165
+
166
+ // 2. Extract TJP (Timeline Identifier)
167
+ // Simplified logic mirroring getGibInfo
168
+ const gib = ibGib.gib || '';
169
+ let tjpGib = gib;
170
+
171
+ if (gib.includes('.')) {
172
+ // It's a frame in a timeline: "punctiliarHash.tjpHash"
173
+ // The TJP is the suffix.
174
+ const parts = gib.split('.');
175
+ tjpGib = parts.slice(1).join('.');
176
+ }
177
+ // Else: It's a Primitive or a TJP itself (Genesis).
178
+ // If Genesis (isTjp=true), the gib IS the tjpGib.
179
+
180
+ const addr = getIbGibAddr({ ibGib });
181
+ this.timelineHeads.set(tjpGib, addr);
182
+ }
183
+ }
184
+
185
+ // ===========================================================================
186
+ // SUITE A: STRATEGY VECTORS (The Math)
187
+ // ===========================================================================
188
+
189
+ await respecfully(sir, 'Suite A: Strategy Vectors (HashRevealV1)', async () => {
190
+
191
+ // Setup generic variables
192
+ const masterSecret = "TestSecret_12345";
193
+ const salt = "TestPool";
194
+ let config: KeystonePoolConfig_HashV1;
195
+
196
+ firstOfAll(sir, async () => {
197
+ // Use our standard builder to get a valid config object
198
+ config = createStandardPoolConfig(salt) as KeystonePoolConfig_HashV1;
199
+ });
200
+
201
+ await respecfully(sir, 'Derivation Logic', async () => {
202
+
203
+ await ifWeMight(sir, 'derivePoolSecret with same inputs returns same output', async () => {
204
+ const strategy = KeystoneStrategyFactory.create({ config });
205
+
206
+ const secretA = await strategy.derivePoolSecret({ masterSecret });
207
+ const secretB = await strategy.derivePoolSecret({ masterSecret });
208
+
209
+ iReckon(sir, secretA).asTo('secret consistency').willEqual(secretB);
210
+ iReckon(sir, secretA).asTo('secret length').isGonnaBeTruthy();
211
+ });
212
+
213
+ await ifWeMight(sir, 'derivePoolSecret with different master secret returns different output', async () => {
214
+ const strategy = KeystoneStrategyFactory.create({ config });
215
+
216
+ const secretA = await strategy.derivePoolSecret({ masterSecret });
217
+ const secretB = await strategy.derivePoolSecret({ masterSecret: masterSecret + "_diff" });
218
+
219
+ iReckon(sir, secretA).asTo('secrets differ').not.willEqual(secretB);
220
+ });
221
+
222
+ await ifWeMight(sir, 'derivePoolSecret with different salt returns different output', async () => {
223
+ // Modify salt in a copy of config
224
+ const configB = { ...config, salt: "OtherPool" };
225
+ const strategyA = KeystoneStrategyFactory.create({ config });
226
+ const strategyB = KeystoneStrategyFactory.create({ config: configB });
227
+
228
+ const secretA = await strategyA.derivePoolSecret({ masterSecret });
229
+ const secretB = await strategyB.derivePoolSecret({ masterSecret });
230
+
231
+ iReckon(sir, secretA).asTo('salt affects secret').not.willEqual(secretB);
232
+ });
233
+ });
234
+
235
+ await respecfully(sir, 'Challenge/Solution Logic', async () => {
236
+
237
+ await ifWeMight(sir, 'generateSolution -> generateChallenge -> validateSolution loop works', async () => {
238
+ const strategy = KeystoneStrategyFactory.create({ config });
239
+ const poolSecret = await strategy.derivePoolSecret({ masterSecret });
240
+ const challengeId = "a3ff7843552870fc28bef2b"; // arbitrary random challengeId
241
+
242
+ // 1. Generate Solution
243
+ const solution = await strategy.generateSolution({ poolSecret, poolId: salt, challengeId });
244
+ iReckon(sir, solution.value).asTo('solution value exists').isGonnaBeTruthy();
245
+ iReckon(sir, solution.challengeId).asTo('id matches').willEqual(challengeId);
246
+
247
+ // 2. Generate Public Challenge from Solution
248
+ const challenge = await strategy.generateChallenge({ solution });
249
+ iReckon(sir, challenge.hash).asTo('challenge hash exists').isGonnaBeTruthy();
250
+
251
+ // 3. Validate
252
+ const isValid = await strategy.validateSolution({ solution, challenge });
253
+ iReckon(sir, isValid).asTo('valid pair should pass').isGonnaBeTrue();
254
+ });
255
+
256
+ await ifWeMight(sir, 'validateSolution fails for mismatched values', async () => {
257
+ const strategy = KeystoneStrategyFactory.create({ config });
258
+ const poolSecret = await strategy.derivePoolSecret({ masterSecret });
259
+ const challengeId = "8c994f3ed598f150e25513"; // arbitrary random challengeId
260
+
261
+ // Generate real pair
262
+ const solution = await strategy.generateSolution({ poolSecret, poolId: salt, challengeId });
263
+ const challenge = await strategy.generateChallenge({ solution });
264
+
265
+ // Tamper with solution value
266
+ const badSolution = { ...solution, value: "hacked_value" };
267
+
268
+ const isValid = await strategy.validateSolution({ solution: badSolution, challenge });
269
+ iReckon(sir, isValid).asTo('tampered solution should fail').isGonnaBeFalse();
270
+ });
271
+
272
+ await ifWeMight(sir, 'validateSolution fails for mismatched challenge hashes', async () => {
273
+ const strategy = KeystoneStrategyFactory.create({ config });
274
+ const poolSecret = await strategy.derivePoolSecret({ masterSecret });
275
+
276
+ // Generate pair A
277
+ const challengeId_A = "416c38cfd6ee63dbf8d4e5ef36"; // arbitrary random challengeId
278
+ const solutionA = await strategy.generateSolution({ poolSecret, poolId: salt, challengeId: challengeId_A });
279
+
280
+ // Generate pair B
281
+ const challengeId_B = "c487ef6b7878fae798c3"; // arbitrary random challengeId
282
+ const solutionB = await strategy.generateSolution({ poolSecret, poolId: salt, challengeId: challengeId_B });
283
+ const challengeB = await strategy.generateChallenge({ solution: solutionB });
284
+
285
+ // Check A against B
286
+ const isValid = await strategy.validateSolution({ solution: solutionA, challenge: challengeB });
287
+ iReckon(sir, isValid).asTo('mismatched pair should fail').isGonnaBeFalse();
288
+ });
289
+ });
290
+ });
291
+
292
+ // ===========================================================================
293
+ // SUITE B: SERVICE LIFECYCLE (Genesis -> Sign -> Validate)
294
+ // ===========================================================================
295
+
296
+ await respecfully(sir, 'Suite B: Service Lifecycle', async () => {
297
+
298
+ const service = new KeystoneService_V1();
299
+ const masterSecret = "AliceSecretKey_987654321";
300
+
301
+ let mockSpace: MockIbGibSpace;
302
+ let mockMetaspace: any;
303
+
304
+ let genesisKeystone: KeystoneIbGib_V1;
305
+ let signedKeystone: KeystoneIbGib_V1;
306
+
307
+ firstOfAll(sir, async () => {
308
+ mockSpace = new MockIbGibSpace();
309
+ mockMetaspace = new MockMetaspaceService(mockSpace);
310
+ });
311
+
312
+ await respecfully(sir, 'Genesis', async () => {
313
+ await ifWeMight(sir, 'creates a valid genesis frame and persists it', async () => {
314
+ const config = createStandardPoolConfig(POOL_ID_DEFAULT);
315
+
316
+ genesisKeystone = await service.genesis({
317
+ masterSecret,
318
+ configs: [config],
319
+ metaspace: mockMetaspace,
320
+ space: mockSpace as any,
321
+ });
322
+
323
+ // Verify Object
324
+ iReckon(sir, genesisKeystone).asTo('genesis object').isGonnaBeTruthy();
325
+ iReckon(sir, genesisKeystone.data?.isTjp).asTo('isTjp').isGonnaBeTrue();
326
+
327
+ // Verify Persistence
328
+ const addr = getIbGibAddr({ ibGib: genesisKeystone });
329
+ const saved = await mockSpace.get({ addr });
330
+
331
+ iReckon(sir, saved).asTo('persisted to space').isGonnaBeTruthy();
332
+
333
+ // Verify Registration (Timeline Tracking)
334
+ // Genesis gib should be registered as a timeline head
335
+ const head = mockMetaspace.timelineHeads.get(genesisKeystone.gib!);
336
+ iReckon(sir, head).asTo('genesis registered as timeline head').willEqual(addr);
337
+ });
338
+ });
339
+
340
+ await respecfully(sir, 'Signing (Evolution)', async () => {
341
+ await ifWeMight(sir, 'evolves the keystone with a valid proof', async () => {
342
+ const claim: Partial<KeystoneClaim> = {
343
+ target: "comment 123^gib",
344
+ verb: "post"
345
+ };
346
+
347
+ const details = { note: "First post!" };
348
+
349
+ signedKeystone = await service.sign({
350
+ latestKeystone: genesisKeystone,
351
+ masterSecret,
352
+ claim,
353
+ poolId: POOL_ID_DEFAULT,
354
+ frameDetails: details,
355
+ metaspace: mockMetaspace,
356
+ space: mockSpace as any,
357
+ });
358
+
359
+ iReckon(sir, signedKeystone).asTo('new frame created').isGonnaBeTruthy();
360
+ iReckon(sir, signedKeystone).asTo('is different frame').not.isGonnaBe(genesisKeystone);
361
+
362
+ // NOTE: If this fails, check if 'sign' calls 'space.put' or 'metaspace.put'!
363
+ // In your current 'sign' implementation, you return the object but might have missed the save step.
364
+ const addr = getIbGibAddr({ ibGib: signedKeystone });
365
+ const saved = await mockSpace.get({ addr });
366
+ iReckon(sir, saved).asTo('persisted to space').isGonnaBeTruthy();
367
+ });
368
+ });
369
+
370
+ await respecfully(sir, 'Validation', async () => {
371
+ await ifWeMight(sir, 'validates the genesis->signed transition', async () => {
372
+ const errors = await service.validate({
373
+ prevIbGib: genesisKeystone,
374
+ currentIbGib: signedKeystone,
375
+ // metaspace: mockMetaspace,
376
+ // space: mockSpace as any,
377
+ });
378
+
379
+ iReckon(sir, errors.length).asTo('signature validation has no errors').willEqual(0);
380
+ });
381
+ });
382
+ });
383
+
384
+ // ===========================================================================
385
+ // SUITE C: SECURITY & SAD PATHS
386
+ // ===========================================================================
387
+
388
+ await respecfully(sir, 'Suite C: Security Vectors', async () => {
389
+
390
+ const service = new KeystoneService_V1();
391
+ const aliceSecret = "AliceSecret_111";
392
+ const eveSecret = "EveSecret_666";
393
+
394
+ let mockSpace: MockIbGibSpace;
395
+ let mockMetaspace: any;
396
+ let genesisKeystone: KeystoneIbGib_V1;
397
+
398
+ firstOfAll(sir, async () => {
399
+ mockSpace = new MockIbGibSpace();
400
+ mockMetaspace = new MockMetaspaceService(mockSpace);
401
+
402
+ // Setup Alice's Identity
403
+ const config = createStandardPoolConfig(POOL_ID_DEFAULT);
404
+ config.behavior.size = 10;
405
+ genesisKeystone = await service.genesis({
406
+ masterSecret: aliceSecret,
407
+ configs: [config],
408
+ metaspace: mockMetaspace,
409
+ space: mockSpace as any,
410
+ });
411
+ });
412
+
413
+ await respecfully(sir, 'Wrong Secret (Forgery)', async () => {
414
+ await ifWeMight(sir, 'prevents creation of forged frames', async () => {
415
+ const claim: Partial<KeystoneClaim> = { target: "comment 123^gib", verb: "post" };
416
+
417
+ let errorCaught = false;
418
+ let errorMsg = "";
419
+
420
+ try {
421
+ // Eve tries to sign Alice's keystone.
422
+ // This MUST fail because sign() calls evolve(), which calls validate().
423
+ await service.sign({
424
+ latestKeystone: genesisKeystone,
425
+ masterSecret: eveSecret,
426
+ claim,
427
+ poolId: POOL_ID_DEFAULT,
428
+ metaspace: mockMetaspace,
429
+ space: mockSpace as any,
430
+ });
431
+ } catch (e: any) {
432
+ errorCaught = true;
433
+ errorMsg = e.message;
434
+ }
435
+
436
+ iReckon(sir, errorCaught).asTo('service rejected forgery').isGonnaBeTrue();
437
+ // Verify it was a crypto error, not something else
438
+ iReckon(sir, errorMsg).asTo('error mentions crypto violation').includes('Crypto Violation');
439
+ });
440
+ });
441
+
442
+ await respecfully(sir, 'Policy Violation (Restricted Verbs)', async () => {
443
+ await ifWeMight(sir, 'throws error if signing forbidden verb with restricted pool', async () => {
444
+ // Create a specific restricted pool config manually
445
+ const restrictedPoolId = "read_only_pool";
446
+ const restrictedConfig = createStandardPoolConfig(restrictedPoolId);
447
+ // Manually restrict it (since Builder defaults to undefined/allow-all)
448
+ restrictedConfig.allowedVerbs = ['read'];
449
+
450
+ const restrictedGenesis = await service.genesis({
451
+ masterSecret: aliceSecret,
452
+ configs: [restrictedConfig],
453
+ metaspace: mockMetaspace,
454
+ space: mockSpace as any,
455
+ });
456
+
457
+ // Try to sign "write" using "read_only_pool"
458
+ const claim: Partial<KeystoneClaim> = { target: "data^gib", verb: "write" };
459
+
460
+ let errorCaught = false;
461
+ try {
462
+ await service.sign({
463
+ latestKeystone: restrictedGenesis,
464
+ masterSecret: aliceSecret,
465
+ claim,
466
+ poolId: restrictedPoolId, // Force use of restricted pool
467
+ metaspace: mockMetaspace,
468
+ space: mockSpace as any,
469
+ });
470
+ } catch (e) {
471
+ errorCaught = true;
472
+ // Optional: Check error message contains "not authorized"
473
+ }
474
+
475
+ iReckon(sir, errorCaught).asTo('policy enforced').isGonnaBeTrue();
476
+ });
477
+ });
478
+ });
479
+
480
+ // ===========================================================================
481
+ // SUITE D: REVOCATION
482
+ // ===========================================================================
483
+
484
+ await respecfullyDear(sir, 'Suite D: Revocation', async () => {
485
+
486
+ const service = new KeystoneService_V1();
487
+ const masterSecret = "AliceSecret_RevokeTest";
488
+
489
+ let mockSpace: MockIbGibSpace;
490
+ let mockMetaspace: any;
491
+ let genesisKeystone: KeystoneIbGib_V1;
492
+
493
+ firstOfAll(sir, async () => {
494
+ mockSpace = new MockIbGibSpace();
495
+ mockMetaspace = new MockMetaspaceService(mockSpace);
496
+
497
+ // Setup Identity WITH a Revocation Pool
498
+ const stdConfig = createStandardPoolConfig(POOL_ID_DEFAULT);
499
+ const revokeConfig = createRevocationPoolConfig(POOL_ID_REVOKE); // Special Config
500
+
501
+ genesisKeystone = await service.genesis({
502
+ masterSecret,
503
+ configs: [stdConfig, revokeConfig],
504
+ metaspace: mockMetaspace,
505
+ space: mockSpace as any,
506
+ });
507
+ });
508
+
509
+ await respecfully(sir, 'Revoke Lifecycle', async () => {
510
+ let revokedKeystone: KeystoneIbGib_V1;
511
+
512
+ await ifWeMight(sir, 'successfully creates a revocation frame', async () => {
513
+ revokedKeystone = await service.revoke({
514
+ latestKeystone: genesisKeystone,
515
+ masterSecret,
516
+ reason: "Key compromised",
517
+ metaspace: mockMetaspace,
518
+ space: mockSpace as any,
519
+ });
520
+
521
+ iReckon(sir, revokedKeystone).isGonnaBeTruthy();
522
+
523
+ // Check Data
524
+ const data = revokedKeystone.data!;
525
+ iReckon(sir, data.revocationInfo).asTo('revocation info present').isGonnaBeTruthy();
526
+ iReckon(sir, data.revocationInfo!.reason).willEqual("Key compromised");
527
+ iReckon(sir, data.revocationInfo!.proof.claim.verb).willEqual(KEYSTONE_VERB_REVOKE);
528
+ });
529
+
530
+ await ifWeMight(sir, 'validates the revocation frame', async () => {
531
+ const errors = await service.validate({
532
+ prevIbGib: genesisKeystone,
533
+ currentIbGib: revokedKeystone!,
534
+ // metaspace: mockMetaspace,
535
+ // space: mockSpace as any,
536
+ });
537
+
538
+ iReckon(sir, errors.length).asTo('no validation errors').willEqual(0);
539
+ });
540
+
541
+ await ifWeMight(sir, 'consumed the revocation pool (Scorched Earth)', async () => {
542
+ const data = revokedKeystone!.data!;
543
+ const revokePool = data.challengePools.find(p => p.id === POOL_ID_REVOKE);
544
+
545
+ // The pool should exist...
546
+ iReckon(sir, revokePool).isGonnaBeTruthy();
547
+
548
+ // Should be empty (0 challenges)
549
+ const remaining = Object.keys(revokePool!.challenges);
550
+ iReckon(sir, remaining.length).asTo('pool depleted').willEqual(0);
551
+ });
552
+ });
553
+ });
554
+
555
+ // ===========================================================================
556
+ // SUITE E: STRUCTURAL EVOLUTION (addPools)
557
+ // ===========================================================================
558
+
559
+ await respecfullyDear(sir, 'Suite E: Structural Evolution (addPools)', async () => {
560
+
561
+ const service = new KeystoneService_V1();
562
+ const aliceSecret = "Alice_Master_Key";
563
+ const bobSecret = "Bob_Foreign_Key";
564
+
565
+ let mockSpace: MockIbGibSpace;
566
+ let mockMetaspace: any;
567
+ let aliceKeystone: KeystoneIbGib_V1;
568
+
569
+ // Helper to generate a "Foreign" pool (e.g. from Bob)
570
+ const createForeignPool = async (id: string, verbs: string[] = []): Promise<KeystoneChallengePool> => {
571
+ const config = createStandardPoolConfig(id);
572
+ config.allowedVerbs = verbs;
573
+
574
+ // We use the factory manually here to simulate Bob doing this offline
575
+ const strategy = KeystoneStrategyFactory.create({ config });
576
+ const poolSecret = await strategy.derivePoolSecret({ masterSecret: bobSecret });
577
+ const challenges: { [id: string]: any } = {};
578
+ const bindingMap: { [char: string]: string[] } = {};
579
+
580
+ for (let i = 0; i < 10; i++) {
581
+ // Manually replicate ID gen for test
582
+ const raw = await hash({ s: `${config.salt}${Date.now()}${i}` });
583
+ const challengeId = raw.substring(0, 16);
584
+
585
+ const solution = await strategy.generateSolution({
586
+ poolSecret, poolId: config.salt, challengeId,
587
+ });
588
+ const challenge = await strategy.generateChallenge({ solution });
589
+ challenges[challengeId] = challenge;
590
+ addToBindingMap(bindingMap, challengeId);
591
+ }
592
+
593
+ return {
594
+ id,
595
+ config,
596
+ challenges,
597
+ bindingMap,
598
+ isForeign: true,
599
+ metadata: { owner: 'Bob' }
600
+ };
601
+ };
602
+
603
+ firstOfAll(sir, async () => {
604
+ mockSpace = new MockIbGibSpace();
605
+ mockMetaspace = new MockMetaspaceService(mockSpace);
606
+
607
+ // Alice Genesis: Standard pool (allows all verbs, including 'manage')
608
+ const config = createStandardPoolConfig(POOL_ID_DEFAULT);
609
+ aliceKeystone = await service.genesis({
610
+ masterSecret: aliceSecret,
611
+ configs: [config],
612
+ metaspace: mockMetaspace,
613
+ space: mockSpace as any,
614
+ });
615
+ });
616
+
617
+ await respecfully(sir, 'Happy Path', async () => {
618
+ await ifWeMight(sir, 'authorizes and adds a foreign pool', async () => {
619
+ const bobPool = await createForeignPool("pool_bob", ["post"]);
620
+
621
+ const updatedKeystone = await service.addPools({
622
+ latestKeystone: aliceKeystone,
623
+ masterSecret: aliceSecret,
624
+ newPools: [bobPool],
625
+ metaspace: mockMetaspace,
626
+ space: mockSpace as any,
627
+ });
628
+
629
+ // 1. Verify new state
630
+ iReckon(sir, updatedKeystone).isGonnaBeTruthy();
631
+ const pools = updatedKeystone.data!.challengePools;
632
+ iReckon(sir, pools.length).asTo('pool count increased').willEqual(2);
633
+
634
+ const foundBob = pools.find(p => p.id === "pool_bob");
635
+ iReckon(sir, foundBob).asTo('bob pool exists').isGonnaBeTruthy();
636
+ iReckon(sir, foundBob!.isForeign).asTo('isForeign flag').isGonnaBeTrue();
637
+
638
+ // 2. Verify Proof
639
+ const proof = updatedKeystone.data!.proofs[0];
640
+ iReckon(sir, proof.claim.verb).asTo('proof verb').willEqual("manage");
641
+ // Alice signed this using HER pool (default), not Bob's
642
+ iReckon(sir, proof.solutions[0].poolId).willEqual(POOL_ID_DEFAULT);
643
+
644
+ // 3. Verify Validity
645
+ const errors = await service.validate({
646
+ prevIbGib: aliceKeystone,
647
+ currentIbGib: updatedKeystone,
648
+ });
649
+ iReckon(sir, errors.length).asTo('validation passed').willEqual(0);
650
+
651
+ // Update local ref for next tests
652
+ aliceKeystone = updatedKeystone;
653
+ });
654
+ });
655
+
656
+ await respecfully(sir, 'Permissions & Logic', async () => {
657
+ await ifWeMight(sir, 'fails if no pool allows "manage" verb', async () => {
658
+ // 1. Create a restricted keystone
659
+ const restrictedConfig = createStandardPoolConfig("read_only");
660
+ restrictedConfig.allowedVerbs = ['read']; // No 'manage'
661
+
662
+ const restrictedKeystone = await service.genesis({
663
+ masterSecret: aliceSecret,
664
+ configs: [restrictedConfig],
665
+ metaspace: mockMetaspace,
666
+ space: mockSpace as any,
667
+ });
668
+
669
+ const newPool = await createForeignPool("pool_test");
670
+ let errorCaught = false;
671
+
672
+ try {
673
+ await service.addPools({
674
+ latestKeystone: restrictedKeystone,
675
+ masterSecret: aliceSecret,
676
+ newPools: [newPool],
677
+ metaspace: mockMetaspace,
678
+ space: mockSpace as any,
679
+ });
680
+ } catch (e: any) {
681
+ errorCaught = true;
682
+ // Should fail in resolveTargetPool or addPools logic
683
+ // console.log("Caught expected error:", e.message);
684
+ }
685
+
686
+ iReckon(sir, errorCaught).asTo('permission denied').isGonnaBeTrue();
687
+ });
688
+
689
+ await ifWeMight(sir, 'fails on ID collision', async () => {
690
+ // Try to add "pool_bob" again (it was added in Happy Path)
691
+ const duplicatePool = await createForeignPool("pool_bob");
692
+
693
+ let errorCaught = false;
694
+ try {
695
+ await service.addPools({
696
+ latestKeystone: aliceKeystone, // This already has pool_bob
697
+ masterSecret: aliceSecret,
698
+ newPools: [duplicatePool],
699
+ metaspace: mockMetaspace,
700
+ space: mockSpace as any,
701
+ });
702
+ } catch (e: any) {
703
+ errorCaught = true;
704
+ iReckon(sir, e.message).includes("ID collision");
705
+ }
706
+
707
+ iReckon(sir, errorCaught).asTo('collision detected').isGonnaBeTrue();
708
+ });
709
+ });
710
+ });
711
+
712
+ // ===========================================================================
713
+ // SUITE E: STRUCTURAL EVOLUTION (addPools)
714
+ // ===========================================================================
715
+
716
+ await respecfullyDear(sir, 'Suite E: Structural Evolution (addPools)', async () => {
717
+
718
+ const service = new KeystoneService_V1();
719
+ const aliceSecret = "Alice_Master_Key";
720
+ const bobSecret = "Bob_Foreign_Key";
721
+
722
+ let mockSpace: MockIbGibSpace;
723
+ let mockMetaspace: any;
724
+ let aliceKeystone: KeystoneIbGib_V1;
725
+
726
+ // Helper to simulate Bob creating a pool "offline" to give to Alice
727
+ const createForeignPool = async (id: string, verbs: string[] = []): Promise<KeystoneChallengePool> => {
728
+ const config = createStandardPoolConfig(id);
729
+ config.allowedVerbs = verbs;
730
+
731
+ // We use the factory manually here to simulate Bob doing this on his own machine
732
+ const strategy = KeystoneStrategyFactory.create({ config });
733
+ const poolSecret = await strategy.derivePoolSecret({ masterSecret: bobSecret });
734
+ const challenges: { [id: string]: any } = {};
735
+ const bindingMap: { [char: string]: string[] } = {};
736
+
737
+ // Generate a small set of challenges
738
+ for (let i = 0; i < 10; i++) {
739
+ const raw = await hash({ s: `${config.salt}${Date.now()}${i}` });
740
+ const challengeId = raw.substring(0, 16);
741
+
742
+ const solution = await strategy.generateSolution({
743
+ poolSecret, poolId: config.salt, challengeId,
744
+ });
745
+ const challenge = await strategy.generateChallenge({ solution });
746
+ challenges[challengeId] = challenge;
747
+ addToBindingMap(bindingMap, challengeId);
748
+ }
749
+
750
+ return {
751
+ id,
752
+ config,
753
+ challenges,
754
+ bindingMap,
755
+ isForeign: true,
756
+ metadata: { owner: 'Bob', role: 'Delegate' }
757
+ };
758
+ };
759
+
760
+ firstOfAll(sir, async () => {
761
+ mockSpace = new MockIbGibSpace();
762
+ mockMetaspace = new MockMetaspaceService(mockSpace);
763
+
764
+ // Alice Genesis: Standard pool (allows all verbs, including 'manage')
765
+ const config = createStandardPoolConfig(POOL_ID_DEFAULT);
766
+ aliceKeystone = await service.genesis({
767
+ masterSecret: aliceSecret,
768
+ configs: [config],
769
+ metaspace: mockMetaspace,
770
+ space: mockSpace as any,
771
+ });
772
+ });
773
+
774
+ await respecfully(sir, 'Happy Path', async () => {
775
+ await ifWeMight(sir, 'authorizes and adds a foreign pool', async () => {
776
+ const bobPool = await createForeignPool("pool_bob", ["post"]);
777
+
778
+ const updatedKeystone = await service.addPools({
779
+ latestKeystone: aliceKeystone,
780
+ masterSecret: aliceSecret,
781
+ newPools: [bobPool],
782
+ metaspace: mockMetaspace,
783
+ space: mockSpace as any,
784
+ });
785
+
786
+ // 1. Verify new state
787
+ iReckon(sir, updatedKeystone).isGonnaBeTruthy();
788
+ const pools = updatedKeystone.data!.challengePools;
789
+ iReckon(sir, pools.length).asTo('pool count increased').willEqual(2);
790
+
791
+ const foundBob = pools.find(p => p.id === "pool_bob");
792
+ iReckon(sir, foundBob).asTo('bob pool exists').isGonnaBeTruthy();
793
+ iReckon(sir, foundBob!.isForeign).asTo('isForeign flag').isGonnaBeTrue();
794
+
795
+ // 2. Verify Proof
796
+ const proof = updatedKeystone.data!.proofs[0];
797
+ iReckon(sir, proof.claim.verb).asTo('proof verb').willEqual(KEYSTONE_VERB_MANAGE);
798
+ // Alice signed this using HER pool (default), not Bob's
799
+ iReckon(sir, proof.solutions[0].poolId).willEqual(POOL_ID_DEFAULT);
800
+
801
+ // 3. Verify Validity
802
+ const errors = await service.validate({
803
+ prevIbGib: aliceKeystone,
804
+ currentIbGib: updatedKeystone,
805
+ });
806
+ iReckon(sir, errors.length).asTo('validation passed').willEqual(0);
807
+
808
+ // Update local ref for next tests
809
+ aliceKeystone = updatedKeystone;
810
+ });
811
+ });
812
+
813
+ await respecfully(sir, 'Permissions & Logic', async () => {
814
+ await ifWeMight(sir, 'fails if no pool allows "manage" verb', async () => {
815
+ // 1. Create a restricted keystone (read-only)
816
+ const restrictedConfig = createStandardPoolConfig("read_only");
817
+ restrictedConfig.allowedVerbs = ['read']; // No 'manage'
818
+
819
+ const restrictedKeystone = await service.genesis({
820
+ masterSecret: aliceSecret,
821
+ configs: [restrictedConfig],
822
+ metaspace: mockMetaspace,
823
+ space: mockSpace as any,
824
+ });
825
+
826
+ const newPool = await createForeignPool("pool_test");
827
+ let errorCaught = false;
828
+
829
+ try {
830
+ await service.addPools({
831
+ latestKeystone: restrictedKeystone,
832
+ masterSecret: aliceSecret,
833
+ newPools: [newPool],
834
+ metaspace: mockMetaspace,
835
+ space: mockSpace as any,
836
+ });
837
+ } catch (e: any) {
838
+ errorCaught = true;
839
+ // Optional: Check error message
840
+ // iReckon(sir, e.message).includes("No local pool found with 'manage'");
841
+ }
842
+
843
+ iReckon(sir, errorCaught).asTo('permission denied').isGonnaBeTrue();
844
+ });
845
+
846
+ await ifWeMight(sir, 'fails on ID collision', async () => {
847
+ // Try to add "pool_bob" again (it was added in Happy Path)
848
+ const duplicatePool = await createForeignPool("pool_bob");
849
+
850
+ let errorCaught = false;
851
+ try {
852
+ await service.addPools({
853
+ latestKeystone: aliceKeystone, // This already has pool_bob
854
+ masterSecret: aliceSecret,
855
+ newPools: [duplicatePool],
856
+ metaspace: mockMetaspace,
857
+ space: mockSpace as any,
858
+ });
859
+ } catch (e: any) {
860
+ errorCaught = true;
861
+ iReckon(sir, e.message).includes("collision");
862
+ }
863
+
864
+ iReckon(sir, errorCaught).asTo('collision detected').isGonnaBeTrue();
865
+ });
866
+ });
867
+ });
868
+
869
+ // ===========================================================================
870
+ // SUITE F: DEEP INSPECTION (Granularity & Serialization)
871
+ // ===========================================================================
872
+
873
+ await respecfullyDear(sir, 'Suite F: Deep Inspection', async () => {
874
+
875
+ const service = new KeystoneService_V1();
876
+ const aliceSecret = "Alice_Deep_Inspect";
877
+ const salt = "granularity_pool";
878
+
879
+ let mockSpace: MockIbGibSpace;
880
+ let mockMetaspace: any;
881
+ let genesisKeystone: KeystoneIbGib_V1;
882
+
883
+ // We use a specific hybrid config to test exact selection logic
884
+ const hybridConfig = createStandardPoolConfig(salt) as KeystonePoolConfig_HashV1;
885
+ // 2 FIFO + 2 Random = 4 Total per sign
886
+ hybridConfig.behavior.selectSequentially = 2;
887
+ hybridConfig.behavior.selectRandomly = 2;
888
+ hybridConfig.behavior.size = 20; // Small enough to track, large enough to be random
889
+
890
+ firstOfAll(sir, async () => {
891
+ mockSpace = new MockIbGibSpace();
892
+ mockMetaspace = new MockMetaspaceService(mockSpace);
893
+
894
+ genesisKeystone = await service.genesis({
895
+ masterSecret: aliceSecret,
896
+ configs: [hybridConfig],
897
+ metaspace: mockMetaspace,
898
+ space: mockSpace as any,
899
+ });
900
+ });
901
+
902
+ await respecfully(sir, 'Proof Granularity & Math', async () => {
903
+ let signedKeystone: KeystoneIbGib_V1;
904
+
905
+ await ifWeMight(sir, 'generates exactly the expected number of solutions', async () => {
906
+ signedKeystone = await service.sign({
907
+ latestKeystone: genesisKeystone,
908
+ masterSecret: aliceSecret,
909
+ claim: { verb: "post", target: "data^gib" },
910
+ metaspace: mockMetaspace,
911
+ space: mockSpace as any,
912
+ });
913
+
914
+ const proofs = signedKeystone.data!.proofs;
915
+ iReckon(sir, proofs.length).asTo('proof count').willEqual(1);
916
+
917
+ const solutions = proofs[0].solutions;
918
+ // 2 Sequential + 2 Random = 4
919
+ iReckon(sir, solutions.length).asTo('solution count').willEqual(4);
920
+ });
921
+
922
+ await ifWeMight(sir, 'verifies the math manually (White-box Crypto Check)', async () => {
923
+ const proof = signedKeystone.data!.proofs[0];
924
+ const poolSnapshot = genesisKeystone.data!.challengePools.find(p => p.id === salt)!;
925
+
926
+ // We iterate every solution in the proof and MANUALLY verify the hash relationship
927
+ // bypassing the Service's validation logic to ensure the raw math holds up.
928
+
929
+ for (const solution of proof.solutions) {
930
+ // 1. Find the challenge in the *Previous* frame (Genesis)
931
+ const challenge = poolSnapshot.challenges[solution.challengeId];
932
+
933
+ if (!challenge) {
934
+ throw new Error(`Test Failure: Solution references ID ${solution.challengeId} which was not in Genesis pool.`);
935
+ }
936
+
937
+ // 2. Re-implement HashReveal V1 verification logic locally in the test
938
+ // Hash(Salt + Value + Salt)
939
+ // Note: rounds=1 in standard config
940
+ const indexSalt = solution.challengeId;
941
+ const calculatedHash = await hash({
942
+ s: `${indexSalt}${solution.value}${indexSalt}`,
943
+ algorithm: 'SHA-256'
944
+ });
945
+
946
+ // 3. Assert
947
+ iReckon(sir, calculatedHash).asTo(`Manual hash verification for ${solution.challengeId}`).willEqual(challenge.hash);
948
+ }
949
+ });
950
+
951
+ await ifWeMight(sir, 'verifies FIFO logic (Deterministic Selection)', async () => {
952
+ const proof = signedKeystone.data!.proofs[0];
953
+ const poolSnapshot = genesisKeystone.data!.challengePools.find(p => p.id === salt)!;
954
+
955
+ // The first N keys in the pool should be the FIFO targets.
956
+ // Assumption: Object.keys returns insertion order (Standard in modern JS engines)
957
+ const allIds = Object.keys(poolSnapshot.challenges);
958
+ const expectedFifoIds = allIds.slice(0, 2);
959
+
960
+ const solvedIds = proof.solutions.map(s => s.challengeId);
961
+
962
+ // Check that our solution list *includes* the expected FIFO IDs
963
+ const hasFirst = solvedIds.includes(expectedFifoIds[0]);
964
+ const hasSecond = solvedIds.includes(expectedFifoIds[1]);
965
+
966
+ iReckon(sir, hasFirst).asTo(`Solution includes 1st FIFO ID (${expectedFifoIds[0]})`).isGonnaBeTrue();
967
+ iReckon(sir, hasSecond).asTo(`Solution includes 2nd FIFO ID (${expectedFifoIds[1]})`).isGonnaBeTrue();
968
+ });
969
+ });
970
+
971
+ // await respecfully(sir, 'DTO & Serialization', async () => {
972
+
973
+ // await ifWeMight(sir, 'survives a clone/JSON-cycle without corruption', async () => {
974
+ // // 1. Create a DTO (simulate network transmission/storage)
975
+ // // 'clone' does a JSON stringify/parse under the hood (usually) or structured clone.
976
+ // const dto = clone(signedKeystone);
977
+
978
+ // // 2. Structural checks
979
+ // iReckon(sir, dto).asTo('dto exists').isGonnaBeTruthy();
980
+ // iReckon(sir, dto.data).asTo('dto data').isGonnaBeTruthy();
981
+ // iReckon(sir, dto.data!.proofs).asTo('dto proofs').isGonnaBeTruthy();
982
+
983
+ // // 3. Functional check: Can the service validate this DTO?
984
+ // // This ensures no prototypes or hidden properties were lost that the service depends on.
985
+ // const errors = await service.validate({
986
+ // prevIbGib: genesisKeystone,
987
+ // currentIbGib: dto, // Passing the DTO, not the original object
988
+ // });
989
+
990
+ // iReckon(sir, errors.length).asTo('DTO validation errors').willEqual(0);
991
+ // });
992
+
993
+ // await ifWeMight(sir, 'ensures data contains no functions or circular refs', async () => {
994
+ // // A crude but effective test: ensure JSON.stringify doesn't throw
995
+ // // and the result is equal to the object (if we parsed it back).
996
+
997
+ // const jsonStr = JSON.stringify(signedKeystone);
998
+ // const parsed = JSON.parse(jsonStr);
999
+
1000
+ // // Compare specific deep fields
1001
+ // const originalSolution = signedKeystone.data!.proofs[0].solutions[0].value;
1002
+ // const parsedSolution = parsed.data.proofs[0].solutions[0].value;
1003
+
1004
+ // iReckon(sir, parsedSolution).asTo('deep property survives stringify').willEqual(originalSolution);
1005
+
1006
+ // // Ensure no extra properties were lost (rudimentary check)
1007
+ // const origKeys = Object.keys(signedKeystone.data!);
1008
+ // const parsedKeys = Object.keys(parsed.data);
1009
+ // iReckon(sir, parsedKeys.length).asTo('key count matches').willEqual(origKeys.length);
1010
+ // });
1011
+ // });
1012
+ });