@ibgib/core-gib 0.1.8 → 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.
- 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 +382 -4
- 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/package.json +1 -1
- 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 +463 -6
- package/src/keystone/keystone-types.mts +24 -0
|
@@ -2,19 +2,17 @@ import {
|
|
|
2
2
|
respecfully, iReckon, ifWe, firstOfAll, firstOfEach, lastOfAll, lastOfEach, respecfullyDear, ifWeMight
|
|
3
3
|
} from '@ibgib/helper-gib/dist/respec-gib/respec-gib.mjs';
|
|
4
4
|
const maam = `[${import.meta.url}]`, sir = maam;
|
|
5
|
-
|
|
5
|
+
import { clone, hash } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
|
|
6
6
|
import { IbGib_V1 } from '@ibgib/ts-gib/dist/V1/types.mjs';
|
|
7
7
|
import { getIbGibAddr } from '@ibgib/ts-gib/dist/helper.mjs';
|
|
8
|
-
// import { MetaspaceService, } from '@ibgib/core-gib/dist/witness/space/metaspace/metaspace-types.mjs';
|
|
9
|
-
// import { Metaspace_Innerspace } from '@ibgib/core-gib/dist/witness/space/metaspace/metaspace-innerspace/metaspace-innerspace.mjs';
|
|
10
|
-
// import { IbGibSpaceAny } from '@ibgib/core-gib/dist/witness/space/space-base-v1.mjs';
|
|
11
8
|
|
|
12
9
|
import { GLOBAL_LOG_A_LOT } from '../core-constants.mjs';
|
|
13
10
|
import { KeystoneStrategyFactory } from './strategy/keystone-strategy-factory.mjs';
|
|
14
|
-
import { KeystoneClaim, KeystoneIbGib_V1, KeystonePoolConfig_HashV1 } from './keystone-types.mjs';
|
|
11
|
+
import { KeystoneChallengePool, KeystoneClaim, KeystoneIbGib_V1, KeystonePoolConfig_HashV1 } from './keystone-types.mjs';
|
|
15
12
|
import { createRevocationPoolConfig, createStandardPoolConfig } from './keystone-config-builder.mjs';
|
|
16
|
-
import { POOL_ID_DEFAULT, POOL_ID_REVOKE, KEYSTONE_VERB_REVOKE } from './keystone-constants.mjs';
|
|
13
|
+
import { POOL_ID_DEFAULT, POOL_ID_REVOKE, KEYSTONE_VERB_REVOKE, KEYSTONE_VERB_MANAGE } from './keystone-constants.mjs';
|
|
17
14
|
import { KeystoneService_V1 } from './keystone-service-v1.mjs';
|
|
15
|
+
import { addToBindingMap } from './keystone-helpers.mjs';
|
|
18
16
|
|
|
19
17
|
const logalot = GLOBAL_LOG_A_LOT;
|
|
20
18
|
|
|
@@ -553,3 +551,462 @@ await respecfullyDear(sir, 'Suite D: Revocation', async () => {
|
|
|
553
551
|
});
|
|
554
552
|
});
|
|
555
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
|
+
});
|
|
@@ -217,6 +217,30 @@ export interface KeystoneChallengePool {
|
|
|
217
217
|
* Note: A single ID may appear in multiple buckets (Coverage Strategy).
|
|
218
218
|
*/
|
|
219
219
|
bindingMap: { [hexChar: string]: string[] };
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* If true, this pool's secrets are NOT derived from the Keystone's
|
|
223
|
+
* primary Master Secret. They are held by an external entity.
|
|
224
|
+
*
|
|
225
|
+
* ## intent
|
|
226
|
+
*
|
|
227
|
+
* The driving use case for this is signing in with a server "super node"
|
|
228
|
+
* and giving that node the ability to sign on behalf of the user. This is a
|
|
229
|
+
* common pattern in SSO-type workflows.
|
|
230
|
+
*/
|
|
231
|
+
isForeign?: boolean;
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Arbitrary metadata for the wallet/user to identify the pool.
|
|
235
|
+
* e.g. { delegate: "PrimaryServer", purpose: "SSO" }
|
|
236
|
+
*
|
|
237
|
+
* ## intent
|
|
238
|
+
*
|
|
239
|
+
* The driving use case for this is signing in with a server "super node"
|
|
240
|
+
* and giving that node the ability to sign on behalf of the user. This is a
|
|
241
|
+
* common pattern in SSO-type workflows.
|
|
242
|
+
*/
|
|
243
|
+
metadata?: any;
|
|
220
244
|
}
|
|
221
245
|
|
|
222
246
|
/**
|