@ibgib/core-gib 0.1.63 → 0.1.65
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-constants.d.mts +1 -0
- package/dist/keystone/keystone-constants.d.mts.map +1 -1
- package/dist/keystone/keystone-constants.mjs +1 -0
- package/dist/keystone/keystone-constants.mjs.map +1 -1
- package/dist/keystone/keystone-helpers.d.mts +27 -1
- package/dist/keystone/keystone-helpers.d.mts.map +1 -1
- package/dist/keystone/keystone-helpers.mjs +261 -16
- package/dist/keystone/keystone-helpers.mjs.map +1 -1
- package/dist/keystone/keystone-service-v1.d.mts +113 -1
- package/dist/keystone/keystone-service-v1.d.mts.map +1 -1
- package/dist/keystone/keystone-service-v1.mjs +442 -27
- package/dist/keystone/keystone-service-v1.mjs.map +1 -1
- package/dist/keystone/keystone-service-v1.respec.mjs +641 -47
- package/dist/keystone/keystone-service-v1.respec.mjs.map +1 -1
- package/dist/keystone/keystone-types.d.mts +31 -2
- package/dist/keystone/keystone-types.d.mts.map +1 -1
- package/dist/keystone/keystone-types.mjs +15 -0
- package/dist/keystone/keystone-types.mjs.map +1 -1
- package/dist/keystone/policy/profiles/profile-domain.json +2 -61
- package/dist/keystone/policy/profiles/profile-sync.json +2 -16
- package/dist/keystone/policy/schemas/keystone.high.schema.json +1 -1
- package/dist/keystone/policy/schemas/keystone.medium.schema.json +1 -1
- package/package.json +2 -2
- package/src/keystone/keystone-constants.mts +1 -0
- package/src/keystone/keystone-helpers.mts +308 -15
- package/src/keystone/keystone-service-v1.mts +535 -28
- package/src/keystone/keystone-service-v1.respec.mts +2130 -1459
- package/src/keystone/keystone-types.mts +50 -2
- package/src/keystone/policy/profiles/profile-domain.json +3 -62
- package/src/keystone/policy/profiles/profile-sync.json +2 -16
- package/src/keystone/policy/schemas/keystone.high.schema.json +1 -1
- package/src/keystone/policy/schemas/keystone.medium.schema.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { extractErrorMsg, mergeMapsOrArrays_Naive } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
|
|
1
|
+
import { extractErrorMsg, mergeMapsOrArrays_Naive, getUUID } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
|
|
2
2
|
import { getIbGibAddr, getIbAndGib } from '@ibgib/ts-gib/dist/helper.mjs';
|
|
3
3
|
import { IbGibAddr } from '@ibgib/ts-gib/dist/types.mjs';
|
|
4
4
|
import { IbGib_V1 } from '@ibgib/ts-gib/dist/V1/types.mjs';
|
|
@@ -7,18 +7,24 @@ import { GLOBAL_LOG_A_LOT } from '../core-constants.mjs';
|
|
|
7
7
|
import {
|
|
8
8
|
KeystoneData_V1, KeystoneIbGib_V1, KeystonePoolConfig, KeystoneClaim,
|
|
9
9
|
KeystoneChallengePool, KeystoneReplenishStrategy, KeystoneRevocationInfo,
|
|
10
|
-
DelegateKeystoneStatus,
|
|
10
|
+
DelegateKeystoneStatus, KeystoneClaimType,
|
|
11
|
+
ClaimDetails_AddPool, ClaimDetails_RemovePool, ClaimDetails_ReplacePool, ClaimDetails_ChangePassword
|
|
11
12
|
} from './keystone-types.mjs';
|
|
12
13
|
import { KeystoneStrategyFactory } from './strategy/keystone-strategy-factory.mjs';
|
|
14
|
+
import { createStandardPoolConfig } from './keystone-config-builder.mjs';
|
|
13
15
|
import {
|
|
14
16
|
POOL_ID_REVOKE, KEYSTONE_VERB_REVOKE, KEYSTONE_VERB_MANAGE,
|
|
15
|
-
KEYSTONE_CHECKPOINT_FREQUENCY, POOL_ID_MANAGE, KEYSTONE_VERB_SYNC
|
|
17
|
+
KEYSTONE_CHECKPOINT_FREQUENCY, POOL_ID_MANAGE, KEYSTONE_VERB_SYNC,
|
|
18
|
+
POOL_ID_CUSTODIAN_MANAGE,
|
|
19
|
+
KEYSTONE_CONFIG_DEFAULT_SIZE, KEYSTONE_CONFIG_DEFAULT_SEQUENTIAL,
|
|
20
|
+
KEYSTONE_CONFIG_DEFAULT_RANDOM, KEYSTONE_CONFIG_DEFAULT_BINDING
|
|
16
21
|
} from './keystone-constants.mjs';
|
|
17
22
|
import {
|
|
18
23
|
createKeystoneIbGibImpl, evolvePersistAndRegisterKeystone,
|
|
19
24
|
generateOpaqueChallengeId, resolveTargetPool, selectChallengeIds,
|
|
20
25
|
solveAndReplenish, validateKeystoneTransition,
|
|
21
|
-
validateGenesisKeystone, validateKeystoneGraph
|
|
26
|
+
validateGenesisKeystone, validateKeystoneGraph,
|
|
27
|
+
generatePoolChallenges, rotatePoolChallenges, verifyPoolSecret
|
|
22
28
|
} from './keystone-helpers.mjs';
|
|
23
29
|
import { getLatestAddrs } from '../witness/space/space-helper.mjs';
|
|
24
30
|
import { IbGibSpaceAny } from '../witness/space/space-base-v1.mjs';
|
|
@@ -45,12 +51,14 @@ export class KeystoneService_V1 {
|
|
|
45
51
|
configs,
|
|
46
52
|
metaspace,
|
|
47
53
|
space,
|
|
54
|
+
isPrimary,
|
|
48
55
|
}: {
|
|
49
56
|
masterSecret: string;
|
|
50
57
|
frameDetails?: any;
|
|
51
58
|
configs: KeystonePoolConfig[];
|
|
52
59
|
metaspace: MetaspaceService;
|
|
53
60
|
space: IbGibSpaceAny;
|
|
61
|
+
isPrimary?: boolean;
|
|
54
62
|
}): Promise<KeystoneIbGib_V1> {
|
|
55
63
|
const lc = `${this.lc}[${this.genesis.name}]`;
|
|
56
64
|
try {
|
|
@@ -59,24 +67,10 @@ export class KeystoneService_V1 {
|
|
|
59
67
|
const challengePools: KeystoneChallengePool[] = [];
|
|
60
68
|
|
|
61
69
|
for (const config of configs) {
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const targetSize = config.behavior.size;
|
|
67
|
-
const timestamp = Date.now().toString();
|
|
68
|
-
|
|
69
|
-
for (let i = 0; i < targetSize; i++) {
|
|
70
|
-
const challengeId = await generateOpaqueChallengeId({
|
|
71
|
-
salt: config.salt, timestamp, index: i
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
const solution = await strategy.generateSolution({
|
|
75
|
-
poolSecret, poolId: config.id, challengeId,
|
|
76
|
-
});
|
|
77
|
-
const challenge = await strategy.generateChallenge({ solution });
|
|
78
|
-
challenges[challengeId] = challenge;
|
|
79
|
-
}
|
|
70
|
+
const challenges = await generatePoolChallenges({
|
|
71
|
+
config,
|
|
72
|
+
masterSecret
|
|
73
|
+
});
|
|
80
74
|
|
|
81
75
|
challengePools.push({
|
|
82
76
|
id: config.id,
|
|
@@ -88,7 +82,12 @@ export class KeystoneService_V1 {
|
|
|
88
82
|
if (challengePools.length === 0) { throw new Error(`No challenge pools created. (E: 38e538530996940e1f16a8b199995825)`); }
|
|
89
83
|
|
|
90
84
|
const data: KeystoneData_V1 = { challengePools, proofs: [] };
|
|
91
|
-
if (frameDetails
|
|
85
|
+
if (frameDetails || isPrimary !== undefined) {
|
|
86
|
+
data.frameDetails = { ...(frameDetails || {}) };
|
|
87
|
+
if (isPrimary !== undefined) {
|
|
88
|
+
data.frameDetails.isPrimary = isPrimary;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
92
91
|
const keystoneIbGib = await createKeystoneIbGibImpl({ data, metaspace, space });
|
|
93
92
|
return keystoneIbGib;
|
|
94
93
|
} catch (error) {
|
|
@@ -585,10 +584,10 @@ export class KeystoneService_V1 {
|
|
|
585
584
|
|
|
586
585
|
const prevData = latestKeystone.data!;
|
|
587
586
|
|
|
588
|
-
// 1. Identify Authority (Resolve Revoke Pool)
|
|
587
|
+
// 1. Identify Authority (Resolve Revoke Pool by verb)
|
|
589
588
|
const pool = resolveTargetPool({
|
|
590
589
|
pools: prevData.challengePools,
|
|
591
|
-
|
|
590
|
+
verb: KEYSTONE_VERB_REVOKE
|
|
592
591
|
});
|
|
593
592
|
|
|
594
593
|
// 2. Construct Claim
|
|
@@ -720,13 +719,15 @@ export class KeystoneService_V1 {
|
|
|
720
719
|
|
|
721
720
|
if (logalot) { console.log(`${lc} Authorized via pool: ${adminPool.id}`); }
|
|
722
721
|
|
|
723
|
-
// 2. Construct the Management Claim
|
|
722
|
+
// 2. Construct the Management Claim with typed details
|
|
724
723
|
const target = getIbGibAddr({ ibGib: latestKeystone });
|
|
725
724
|
const claim: Partial<KeystoneClaim> = {
|
|
726
725
|
verb: KEYSTONE_VERB_MANAGE,
|
|
727
726
|
target, // I am managing myself
|
|
728
|
-
|
|
729
|
-
|
|
727
|
+
details: {
|
|
728
|
+
type: KeystoneClaimType.add_pool,
|
|
729
|
+
info: { add: newPools.map(p => p.id) } as ClaimDetails_AddPool
|
|
730
|
+
}
|
|
730
731
|
};
|
|
731
732
|
|
|
732
733
|
// 3. Calculate Costs
|
|
@@ -795,6 +796,128 @@ export class KeystoneService_V1 {
|
|
|
795
796
|
}
|
|
796
797
|
}
|
|
797
798
|
|
|
799
|
+
/**
|
|
800
|
+
* Structural evolution: Removes challenge pools from the keystone.
|
|
801
|
+
*
|
|
802
|
+
* Use Case: Unlinking a custodian or delegate SSO pool.
|
|
803
|
+
*
|
|
804
|
+
* Requires the Master Secret to authorize the change via a pool containing
|
|
805
|
+
* the 'manage' verb (the administrative manage pool).
|
|
806
|
+
*/
|
|
807
|
+
async removePools({
|
|
808
|
+
latestKeystone,
|
|
809
|
+
masterSecret,
|
|
810
|
+
poolIds,
|
|
811
|
+
metaspace,
|
|
812
|
+
space,
|
|
813
|
+
}: {
|
|
814
|
+
latestKeystone: KeystoneIbGib_V1;
|
|
815
|
+
/**
|
|
816
|
+
* Alice's Master Secret.
|
|
817
|
+
* Required to solve challenges from the Admin/Manage pool to authorize this change.
|
|
818
|
+
*/
|
|
819
|
+
masterSecret: string;
|
|
820
|
+
/**
|
|
821
|
+
* The IDs of the pools to remove.
|
|
822
|
+
*/
|
|
823
|
+
poolIds: string[];
|
|
824
|
+
metaspace: MetaspaceService;
|
|
825
|
+
space: IbGibSpaceAny;
|
|
826
|
+
}): Promise<KeystoneIbGib_V1> {
|
|
827
|
+
const lc = `${this.lc}[${this.removePools.name}]`;
|
|
828
|
+
try {
|
|
829
|
+
if (logalot) { console.log(`${lc} starting...`); }
|
|
830
|
+
|
|
831
|
+
if (!latestKeystone.data) { throw new Error(`(UNEXPECTED) latestKeystone.data falsy? (E: 8334c8faed128166a999d428c7805b25)`); }
|
|
832
|
+
const prevData = latestKeystone.data;
|
|
833
|
+
|
|
834
|
+
if (prevData.revocationInfo) { throw new Error(`Keystone has been revoked. Cannot remove pools. (E: 9599f8f51c78d722252ddb2894fdbe25)`); }
|
|
835
|
+
|
|
836
|
+
if (poolIds.length === 0) { throw new Error(`No pool IDs provided to remove. (E: 7599f8f51c78d722252ddb2894fdbe25)`); }
|
|
837
|
+
|
|
838
|
+
// 1. Identify Authority (Resolve Admin Pool)
|
|
839
|
+
const adminPool = resolveTargetPool({
|
|
840
|
+
pools: prevData.challengePools,
|
|
841
|
+
verb: KEYSTONE_VERB_MANAGE,
|
|
842
|
+
});
|
|
843
|
+
|
|
844
|
+
if (logalot) { console.log(`${lc} Authorized via pool: ${adminPool.id}`); }
|
|
845
|
+
|
|
846
|
+
// 2. Construct the Management Claim with typed details
|
|
847
|
+
const target = getIbGibAddr({ ibGib: latestKeystone });
|
|
848
|
+
const claim: Partial<KeystoneClaim> = {
|
|
849
|
+
verb: KEYSTONE_VERB_MANAGE,
|
|
850
|
+
target,
|
|
851
|
+
details: {
|
|
852
|
+
type: KeystoneClaimType.remove_pool,
|
|
853
|
+
info: { remove: poolIds } as ClaimDetails_RemovePool
|
|
854
|
+
}
|
|
855
|
+
};
|
|
856
|
+
|
|
857
|
+
// 3. Calculate Costs
|
|
858
|
+
const idsToSolve = await selectChallengeIds({
|
|
859
|
+
pool: adminPool,
|
|
860
|
+
targetAddr: target
|
|
861
|
+
});
|
|
862
|
+
|
|
863
|
+
// 4. Pay the Cost (Solve & Replenish)
|
|
864
|
+
const { proof, nextPools: replenishedExistingPools } = await solveAndReplenish({
|
|
865
|
+
targetPoolId: adminPool.id,
|
|
866
|
+
prevPools: prevData.challengePools,
|
|
867
|
+
masterSecret,
|
|
868
|
+
challengeIds: idsToSolve,
|
|
869
|
+
claim,
|
|
870
|
+
});
|
|
871
|
+
|
|
872
|
+
// 5. Mutate Structure (Remove the pools)
|
|
873
|
+
const removeSet = new Set(poolIds);
|
|
874
|
+
const finalPools = replenishedExistingPools.filter(p => !removeSet.has(p.id));
|
|
875
|
+
|
|
876
|
+
// Verify that we actually removed the expected count of pools
|
|
877
|
+
if (finalPools.length !== replenishedExistingPools.length - poolIds.length) {
|
|
878
|
+
const missingIds = poolIds.filter(id => !replenishedExistingPools.some(p => p.id === id));
|
|
879
|
+
throw new Error(`Cannot remove pool. Pool IDs not found in keystone: ${missingIds.join(', ')} (E: 8a4c2b1d3e5f6a7b8c9d0e1f2a3b4c5e)`);
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
// 6. Construct New Data
|
|
883
|
+
const n = (prevData.n ?? 0) + 1;
|
|
884
|
+
let checkpointDetails: any | undefined;
|
|
885
|
+
if (n % KEYSTONE_CHECKPOINT_FREQUENCY === 0) {
|
|
886
|
+
const currentFrameView: KeystoneIbGib_V1 = {
|
|
887
|
+
...latestKeystone,
|
|
888
|
+
data: { ...prevData, challengePools: finalPools }
|
|
889
|
+
};
|
|
890
|
+
checkpointDetails = await this.getAggregateDetails({
|
|
891
|
+
latestKeystone: currentFrameView,
|
|
892
|
+
metaspace,
|
|
893
|
+
space
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
const newData: KeystoneData_V1 = {
|
|
898
|
+
challengePools: finalPools,
|
|
899
|
+
proofs: [proof],
|
|
900
|
+
checkpointDetails,
|
|
901
|
+
...(prevData.delegates ? { delegates: prevData.delegates } : {}),
|
|
902
|
+
};
|
|
903
|
+
|
|
904
|
+
// 7. Commit
|
|
905
|
+
const newKeystone = await evolvePersistAndRegisterKeystone({
|
|
906
|
+
prevIbGib: latestKeystone,
|
|
907
|
+
newData,
|
|
908
|
+
metaspace,
|
|
909
|
+
space
|
|
910
|
+
});
|
|
911
|
+
|
|
912
|
+
return newKeystone;
|
|
913
|
+
} catch (error) {
|
|
914
|
+
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
915
|
+
throw error;
|
|
916
|
+
} finally {
|
|
917
|
+
if (logalot) { console.log(`${lc} complete.`); }
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
|
|
798
921
|
/**
|
|
799
922
|
* Consolidates the state of a Keystone identity across its history.
|
|
800
923
|
* Walks back until it hits a checkpoint or genesis.
|
|
@@ -854,4 +977,388 @@ export class KeystoneService_V1 {
|
|
|
854
977
|
throw error;
|
|
855
978
|
}
|
|
856
979
|
}
|
|
980
|
+
|
|
981
|
+
/**
|
|
982
|
+
* Retrieves common aggregated info for a keystone timeline.
|
|
983
|
+
*/
|
|
984
|
+
async getKeystoneCommonInfo({
|
|
985
|
+
addr,
|
|
986
|
+
ibGib,
|
|
987
|
+
metaspace,
|
|
988
|
+
space,
|
|
989
|
+
}: {
|
|
990
|
+
addr?: string;
|
|
991
|
+
ibGib?: KeystoneIbGib_V1;
|
|
992
|
+
metaspace: MetaspaceService;
|
|
993
|
+
space: IbGibSpaceAny;
|
|
994
|
+
}): Promise<{
|
|
995
|
+
name: string;
|
|
996
|
+
description: string;
|
|
997
|
+
isPrimary: boolean;
|
|
998
|
+
aggregateDetails: any;
|
|
999
|
+
latestFrameDetails: any;
|
|
1000
|
+
n: number;
|
|
1001
|
+
timestamp?: string;
|
|
1002
|
+
timestampMs?: number;
|
|
1003
|
+
} | null> {
|
|
1004
|
+
const lc = `${this.lc}[getKeystoneCommonInfo]`;
|
|
1005
|
+
try {
|
|
1006
|
+
let targetAddr = addr;
|
|
1007
|
+
let targetIbGib: any = ibGib;
|
|
1008
|
+
if (!targetAddr) {
|
|
1009
|
+
if (!targetIbGib) { return null; }
|
|
1010
|
+
targetAddr = getIbGibAddr({ ibGib: targetIbGib });
|
|
1011
|
+
}
|
|
1012
|
+
if (!targetIbGib) {
|
|
1013
|
+
const { ib, gib } = getIbAndGib({ ibGibAddr: targetAddr });
|
|
1014
|
+
targetIbGib = { ib, gib };
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
const resGetLatest = await getLatestAddrs({
|
|
1018
|
+
ibGibs: [targetIbGib],
|
|
1019
|
+
space,
|
|
1020
|
+
});
|
|
1021
|
+
|
|
1022
|
+
if (!resGetLatest.data?.latestAddrsMap) { return null; }
|
|
1023
|
+
const latestAddr = resGetLatest.data.latestAddrsMap[targetAddr] || Object.values(resGetLatest.data.latestAddrsMap)[0];
|
|
1024
|
+
if (!latestAddr) { return null; }
|
|
1025
|
+
|
|
1026
|
+
const resGet = await metaspace.get({ addr: latestAddr, space });
|
|
1027
|
+
const latestKeystone = resGet.ibGibs?.at(0) as KeystoneIbGib_V1;
|
|
1028
|
+
if (!latestKeystone || !latestKeystone.data) { return null; }
|
|
1029
|
+
|
|
1030
|
+
const aggregateDetails = await this.getAggregateDetails({
|
|
1031
|
+
latestKeystone,
|
|
1032
|
+
metaspace,
|
|
1033
|
+
space
|
|
1034
|
+
});
|
|
1035
|
+
|
|
1036
|
+
const name = aggregateDetails?.username || aggregateDetails?.profile?.name || aggregateDetails?.name || '';
|
|
1037
|
+
const description = aggregateDetails?.description || aggregateDetails?.profile?.description || '';
|
|
1038
|
+
const isPrimary = !!aggregateDetails?.isPrimary || !!aggregateDetails?.profile?.isPrimary;
|
|
1039
|
+
const latestFrameDetails = latestKeystone.data.frameDetails || {};
|
|
1040
|
+
const n = latestKeystone.data.n ?? 0;
|
|
1041
|
+
const timestamp = latestFrameDetails?.timestamp || aggregateDetails?.timestamp || '';
|
|
1042
|
+
const timestampMs = latestFrameDetails?.timestampMs || aggregateDetails?.timestampMs || undefined;
|
|
1043
|
+
|
|
1044
|
+
return {
|
|
1045
|
+
name,
|
|
1046
|
+
description,
|
|
1047
|
+
isPrimary,
|
|
1048
|
+
aggregateDetails,
|
|
1049
|
+
latestFrameDetails,
|
|
1050
|
+
n,
|
|
1051
|
+
timestamp,
|
|
1052
|
+
timestampMs
|
|
1053
|
+
};
|
|
1054
|
+
} catch (error) {
|
|
1055
|
+
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
1056
|
+
return null;
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
/**
|
|
1061
|
+
* Replaces a challenge pool (such as manage or custodian-manage) with a new pool,
|
|
1062
|
+
* authorizing the transition via the specified signing pool.
|
|
1063
|
+
*
|
|
1064
|
+
* @param latestKeystone The current tip frame of the keystone timeline.
|
|
1065
|
+
* - Visibility: Public (Public Merkle frame, safe for logs/ledger).
|
|
1066
|
+
* - Generator: Client/Server (Constructed locally and synced).
|
|
1067
|
+
*
|
|
1068
|
+
* @param newPool The fully constructed new pool to place in the next frame.
|
|
1069
|
+
* - Visibility: Public (Public configuration and public challenges, safe for ledger).
|
|
1070
|
+
* - Generator: Client/Server (Generated dynamically by the party proposing the update).
|
|
1071
|
+
*
|
|
1072
|
+
* @param signingSecret The master secret or derived KDF secret of the pool authorizing the change.
|
|
1073
|
+
* - Visibility: Private (Confidential secret credential. Must never go over the wire).
|
|
1074
|
+
* - Generator: Client/Server (Known only to the signing party).
|
|
1075
|
+
*
|
|
1076
|
+
* @param signingPoolId The ID of the pool used to authorize the transition (defaults to 'manage').
|
|
1077
|
+
* - Visibility: Public (Non-sensitive configuration string).
|
|
1078
|
+
* - Generator: Client/Server (From pool configuration).
|
|
1079
|
+
*/
|
|
1080
|
+
async replacePool({
|
|
1081
|
+
latestKeystone,
|
|
1082
|
+
newPool,
|
|
1083
|
+
signingSecret,
|
|
1084
|
+
signingPoolId = POOL_ID_MANAGE,
|
|
1085
|
+
frameDetails,
|
|
1086
|
+
metaspace,
|
|
1087
|
+
space,
|
|
1088
|
+
}: {
|
|
1089
|
+
latestKeystone: KeystoneIbGib_V1;
|
|
1090
|
+
newPool: KeystoneChallengePool;
|
|
1091
|
+
signingSecret: string;
|
|
1092
|
+
signingPoolId?: string;
|
|
1093
|
+
frameDetails?: any;
|
|
1094
|
+
metaspace: MetaspaceService;
|
|
1095
|
+
space: IbGibSpaceAny;
|
|
1096
|
+
}): Promise<KeystoneIbGib_V1> {
|
|
1097
|
+
const lc = `${this.lc}[${this.replacePool.name}]`;
|
|
1098
|
+
try {
|
|
1099
|
+
if (logalot) { console.log(`${lc} starting... replacing pool: ${newPool.id}`); }
|
|
1100
|
+
|
|
1101
|
+
if (!latestKeystone.data) { throw new Error(`(UNEXPECTED) latestKeystone.data falsy? (E: 1334c8faed128166a999d428c7805b25)`); }
|
|
1102
|
+
const prevData = latestKeystone.data;
|
|
1103
|
+
|
|
1104
|
+
if (prevData.revocationInfo) { throw new Error(`Keystone has been revoked. Cannot replace pool. (E: 2599f8f51c78d722252ddb2894fdbe25)`); }
|
|
1105
|
+
|
|
1106
|
+
// 1. Identify Authority (Resolve Signing Pool)
|
|
1107
|
+
const prevPools = prevData.challengePools;
|
|
1108
|
+
const signingPool = prevPools.find(p => p.id === signingPoolId);
|
|
1109
|
+
if (!signingPool) {
|
|
1110
|
+
throw new Error(`Signing pool not found: ${signingPoolId} (E: 3599f8f51c78d722252ddb2894fdbe25)`);
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
// 2. Construct the Management Claim with typed details
|
|
1114
|
+
const target = getIbGibAddr({ ibGib: latestKeystone });
|
|
1115
|
+
const claim: Partial<KeystoneClaim> = {
|
|
1116
|
+
verb: KEYSTONE_VERB_MANAGE,
|
|
1117
|
+
target,
|
|
1118
|
+
details: {
|
|
1119
|
+
type: KeystoneClaimType.replace_pool,
|
|
1120
|
+
info: { replace: newPool.id } as ClaimDetails_ReplacePool
|
|
1121
|
+
}
|
|
1122
|
+
};
|
|
1123
|
+
|
|
1124
|
+
// 3. Calculate Costs
|
|
1125
|
+
const idsToSolve = await selectChallengeIds({
|
|
1126
|
+
pool: signingPool,
|
|
1127
|
+
targetAddr: target
|
|
1128
|
+
});
|
|
1129
|
+
|
|
1130
|
+
// 4. Pay the Cost (Solve & Replenish)
|
|
1131
|
+
const { proof, nextPools } = await solveAndReplenish({
|
|
1132
|
+
targetPoolId: signingPoolId,
|
|
1133
|
+
prevPools,
|
|
1134
|
+
masterSecret: signingSecret,
|
|
1135
|
+
challengeIds: idsToSolve,
|
|
1136
|
+
claim,
|
|
1137
|
+
});
|
|
1138
|
+
|
|
1139
|
+
// 5. Replace target pool in nextPools
|
|
1140
|
+
const targetIdx = nextPools.findIndex(p => p.id === newPool.id);
|
|
1141
|
+
if (targetIdx === -1) {
|
|
1142
|
+
throw new Error(`Target pool to replace '${newPool.id}' not found in keystone challenge pools.`);
|
|
1143
|
+
}
|
|
1144
|
+
nextPools[targetIdx] = newPool;
|
|
1145
|
+
|
|
1146
|
+
// 6. Construct New Data
|
|
1147
|
+
const n = (prevData.n ?? 0) + 1;
|
|
1148
|
+
let checkpointDetails: any | undefined;
|
|
1149
|
+
if (n % KEYSTONE_CHECKPOINT_FREQUENCY === 0) {
|
|
1150
|
+
const currentFrameView: KeystoneIbGib_V1 = {
|
|
1151
|
+
...latestKeystone,
|
|
1152
|
+
data: { ...prevData, frameDetails }
|
|
1153
|
+
};
|
|
1154
|
+
checkpointDetails = await this.getAggregateDetails({
|
|
1155
|
+
latestKeystone: currentFrameView,
|
|
1156
|
+
metaspace,
|
|
1157
|
+
space
|
|
1158
|
+
});
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
const newData: KeystoneData_V1 = {
|
|
1162
|
+
challengePools: nextPools,
|
|
1163
|
+
proofs: [proof],
|
|
1164
|
+
frameDetails,
|
|
1165
|
+
checkpointDetails,
|
|
1166
|
+
...(prevData.delegates ? { delegates: prevData.delegates } : {}),
|
|
1167
|
+
};
|
|
1168
|
+
|
|
1169
|
+
// 7. Commit
|
|
1170
|
+
const newKeystone = await evolvePersistAndRegisterKeystone({
|
|
1171
|
+
prevIbGib: latestKeystone,
|
|
1172
|
+
newData,
|
|
1173
|
+
metaspace,
|
|
1174
|
+
space
|
|
1175
|
+
});
|
|
1176
|
+
|
|
1177
|
+
return newKeystone;
|
|
1178
|
+
} catch (error) {
|
|
1179
|
+
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
1180
|
+
throw error;
|
|
1181
|
+
} finally {
|
|
1182
|
+
if (logalot) { console.log(`${lc} complete.`); }
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
/**
|
|
1187
|
+
* Evolve the keystone by rotating the primary user manage pool (`manage`) to a new master secret (passphrase).
|
|
1188
|
+
* This creates a new frame signed by the specified `signingPoolId` using `signingSecret`.
|
|
1189
|
+
*
|
|
1190
|
+
* Supports recovery (signing via `custodian-manage` using the custodian secret) and normal rotation
|
|
1191
|
+
* (signing via `manage` using the old password).
|
|
1192
|
+
*
|
|
1193
|
+
* @param latestKeystone The current tip frame of the keystone.
|
|
1194
|
+
* - Visibility: Public (Public Merkle frame, safe for ledger).
|
|
1195
|
+
* - Generator: Client/Server (Constructed locally and synced).
|
|
1196
|
+
*
|
|
1197
|
+
* @param newMasterSecret The new passphrase/master secret to set for the user's primary pools.
|
|
1198
|
+
* - Visibility: Private (Confidential secret credential. Must never go over the wire).
|
|
1199
|
+
* - Generator: Client (From user input in settings).
|
|
1200
|
+
*
|
|
1201
|
+
* @param signingSecret The secret of the pool authorizing this change (either old passphrase or derived custodian secret).
|
|
1202
|
+
* - Visibility: Private (Confidential secret. Must never go over the wire).
|
|
1203
|
+
* - Generator: Client/Server (Known only to the signing party).
|
|
1204
|
+
*
|
|
1205
|
+
* @param signingPoolId The ID of the pool used to authorize the rotation (defaults to 'manage').
|
|
1206
|
+
* - Visibility: Public (Non-sensitive ID string).
|
|
1207
|
+
* - Generator: Client/Server (From pool configuration).
|
|
1208
|
+
*/
|
|
1209
|
+
async changePassword({
|
|
1210
|
+
latestKeystone,
|
|
1211
|
+
newMasterSecret,
|
|
1212
|
+
signingSecret,
|
|
1213
|
+
signingPoolId = POOL_ID_MANAGE,
|
|
1214
|
+
poolIds,
|
|
1215
|
+
frameDetails,
|
|
1216
|
+
metaspace,
|
|
1217
|
+
space,
|
|
1218
|
+
}: {
|
|
1219
|
+
latestKeystone: KeystoneIbGib_V1;
|
|
1220
|
+
newMasterSecret: string;
|
|
1221
|
+
signingSecret: string;
|
|
1222
|
+
signingPoolId?: string;
|
|
1223
|
+
poolIds?: string[];
|
|
1224
|
+
frameDetails?: any;
|
|
1225
|
+
metaspace: MetaspaceService;
|
|
1226
|
+
space: IbGibSpaceAny;
|
|
1227
|
+
}): Promise<KeystoneIbGib_V1> {
|
|
1228
|
+
const lc = `${this.lc}[${this.changePassword.name}]`;
|
|
1229
|
+
try {
|
|
1230
|
+
if (logalot) { console.log(`${lc} starting...`); }
|
|
1231
|
+
|
|
1232
|
+
if (!latestKeystone.data) { throw new Error(`latestKeystone.data falsy`); }
|
|
1233
|
+
if (latestKeystone.data.revocationInfo) { throw new Error(`Keystone has been revoked. Cannot change password. (E: 8599f8f51c78d722252ddb2894fdbe25)`); }
|
|
1234
|
+
|
|
1235
|
+
const prevPools = latestKeystone.data.challengePools;
|
|
1236
|
+
const signingPool = prevPools.find(p => p.id === signingPoolId);
|
|
1237
|
+
if (!signingPool) {
|
|
1238
|
+
throw new Error(`Signing pool not found: ${signingPoolId} (E: 3599f8f51c78d722252ddb2894fdbe25)`);
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
// 1. Resolve target pools to rotate
|
|
1242
|
+
let targets: KeystoneChallengePool[] = [];
|
|
1243
|
+
if (poolIds && poolIds.length > 0) {
|
|
1244
|
+
for (const id of poolIds) {
|
|
1245
|
+
const found = prevPools.find(p => p.id === id);
|
|
1246
|
+
if (!found) {
|
|
1247
|
+
throw new Error(`Target pool to rotate not found: ${id} (E: 4599f8f51c78d722252ddb2894fdbe25)`);
|
|
1248
|
+
}
|
|
1249
|
+
targets.push(found);
|
|
1250
|
+
}
|
|
1251
|
+
} else {
|
|
1252
|
+
targets = prevPools.filter(p => !p.isForeign);
|
|
1253
|
+
if (targets.length === 0) {
|
|
1254
|
+
throw new Error(`No non-foreign pools found to rotate. (E: 5599f8f51c78d722252ddb2894fdbe25)`);
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
// 2. Rotate the target pools
|
|
1259
|
+
const rotatedPools: KeystoneChallengePool[] = [];
|
|
1260
|
+
for (const targetPool of targets) {
|
|
1261
|
+
const rotated = await rotatePoolChallenges({
|
|
1262
|
+
prevPool: targetPool,
|
|
1263
|
+
newMasterSecret
|
|
1264
|
+
});
|
|
1265
|
+
rotatedPools.push(rotated);
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
// 3. Construct Management Claim with typed details
|
|
1269
|
+
const target = getIbGibAddr({ ibGib: latestKeystone });
|
|
1270
|
+
const claim: Partial<KeystoneClaim> = {
|
|
1271
|
+
verb: KEYSTONE_VERB_MANAGE,
|
|
1272
|
+
target,
|
|
1273
|
+
details: {
|
|
1274
|
+
type: KeystoneClaimType.change_password,
|
|
1275
|
+
info: { change: rotatedPools.map(p => p.id) } as ClaimDetails_ChangePassword
|
|
1276
|
+
}
|
|
1277
|
+
};
|
|
1278
|
+
|
|
1279
|
+
// 4. Calculate Costs
|
|
1280
|
+
const idsToSolve = await selectChallengeIds({
|
|
1281
|
+
pool: signingPool,
|
|
1282
|
+
targetAddr: target
|
|
1283
|
+
});
|
|
1284
|
+
|
|
1285
|
+
// 5. Pay the Cost (Solve & Replenish)
|
|
1286
|
+
const { proof, nextPools } = await solveAndReplenish({
|
|
1287
|
+
targetPoolId: signingPoolId,
|
|
1288
|
+
prevPools,
|
|
1289
|
+
masterSecret: signingSecret,
|
|
1290
|
+
challengeIds: idsToSolve,
|
|
1291
|
+
claim
|
|
1292
|
+
});
|
|
1293
|
+
|
|
1294
|
+
// 6. Swap the rotated pools in nextPools
|
|
1295
|
+
for (const rotated of rotatedPools) {
|
|
1296
|
+
const idx = nextPools.findIndex(p => p.id === rotated.id);
|
|
1297
|
+
if (idx === -1) {
|
|
1298
|
+
throw new Error(`Rotated pool ${rotated.id} not found in next pools list. (E: 6599f8f51c78d722252ddb2894fdbe25)`);
|
|
1299
|
+
}
|
|
1300
|
+
nextPools[idx] = rotated;
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
// 7. Construct New Data
|
|
1304
|
+
const n = (latestKeystone.data.n ?? 0) + 1;
|
|
1305
|
+
let checkpointDetails: any | undefined;
|
|
1306
|
+
if (n % KEYSTONE_CHECKPOINT_FREQUENCY === 0) {
|
|
1307
|
+
const currentFrameView: KeystoneIbGib_V1 = {
|
|
1308
|
+
...latestKeystone,
|
|
1309
|
+
data: { ...latestKeystone.data, challengePools: nextPools }
|
|
1310
|
+
};
|
|
1311
|
+
checkpointDetails = await this.getAggregateDetails({
|
|
1312
|
+
latestKeystone: currentFrameView,
|
|
1313
|
+
metaspace,
|
|
1314
|
+
space
|
|
1315
|
+
});
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
const newData: KeystoneData_V1 = {
|
|
1319
|
+
challengePools: nextPools,
|
|
1320
|
+
proofs: [proof],
|
|
1321
|
+
frameDetails,
|
|
1322
|
+
checkpointDetails,
|
|
1323
|
+
...(latestKeystone.data.delegates ? { delegates: latestKeystone.data.delegates } : {}),
|
|
1324
|
+
};
|
|
1325
|
+
|
|
1326
|
+
// 8. Commit
|
|
1327
|
+
return await evolvePersistAndRegisterKeystone({
|
|
1328
|
+
prevIbGib: latestKeystone,
|
|
1329
|
+
newData,
|
|
1330
|
+
metaspace,
|
|
1331
|
+
space
|
|
1332
|
+
});
|
|
1333
|
+
} catch (error) {
|
|
1334
|
+
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
1335
|
+
throw error;
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
/**
|
|
1340
|
+
* Verifies if a candidate signing secret (passphrase) is correct for a specific challenge pool.
|
|
1341
|
+
* Does not mutate any state.
|
|
1342
|
+
*/
|
|
1343
|
+
async verifySigningSecret({
|
|
1344
|
+
keystoneIbGib,
|
|
1345
|
+
signingSecret,
|
|
1346
|
+
poolId
|
|
1347
|
+
}: {
|
|
1348
|
+
keystoneIbGib: KeystoneIbGib_V1;
|
|
1349
|
+
signingSecret: string;
|
|
1350
|
+
poolId: string;
|
|
1351
|
+
}): Promise<boolean> {
|
|
1352
|
+
const lc = `${this.lc}[${this.verifySigningSecret.name}]`;
|
|
1353
|
+
try {
|
|
1354
|
+
if (!keystoneIbGib.data) return false;
|
|
1355
|
+
const pool = keystoneIbGib.data.challengePools.find(p => p.id === poolId);
|
|
1356
|
+
if (!pool) return false;
|
|
1357
|
+
|
|
1358
|
+
return await verifyPoolSecret({ pool, signingSecret });
|
|
1359
|
+
} catch (error) {
|
|
1360
|
+
console.warn(`${lc} Exception during verification: ${extractErrorMsg(error)}`);
|
|
1361
|
+
return false;
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
857
1364
|
}
|