@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.
Files changed (32) hide show
  1. package/dist/keystone/keystone-constants.d.mts +1 -0
  2. package/dist/keystone/keystone-constants.d.mts.map +1 -1
  3. package/dist/keystone/keystone-constants.mjs +1 -0
  4. package/dist/keystone/keystone-constants.mjs.map +1 -1
  5. package/dist/keystone/keystone-helpers.d.mts +27 -1
  6. package/dist/keystone/keystone-helpers.d.mts.map +1 -1
  7. package/dist/keystone/keystone-helpers.mjs +261 -16
  8. package/dist/keystone/keystone-helpers.mjs.map +1 -1
  9. package/dist/keystone/keystone-service-v1.d.mts +113 -1
  10. package/dist/keystone/keystone-service-v1.d.mts.map +1 -1
  11. package/dist/keystone/keystone-service-v1.mjs +442 -27
  12. package/dist/keystone/keystone-service-v1.mjs.map +1 -1
  13. package/dist/keystone/keystone-service-v1.respec.mjs +641 -47
  14. package/dist/keystone/keystone-service-v1.respec.mjs.map +1 -1
  15. package/dist/keystone/keystone-types.d.mts +31 -2
  16. package/dist/keystone/keystone-types.d.mts.map +1 -1
  17. package/dist/keystone/keystone-types.mjs +15 -0
  18. package/dist/keystone/keystone-types.mjs.map +1 -1
  19. package/dist/keystone/policy/profiles/profile-domain.json +2 -61
  20. package/dist/keystone/policy/profiles/profile-sync.json +2 -16
  21. package/dist/keystone/policy/schemas/keystone.high.schema.json +1 -1
  22. package/dist/keystone/policy/schemas/keystone.medium.schema.json +1 -1
  23. package/package.json +2 -2
  24. package/src/keystone/keystone-constants.mts +1 -0
  25. package/src/keystone/keystone-helpers.mts +308 -15
  26. package/src/keystone/keystone-service-v1.mts +535 -28
  27. package/src/keystone/keystone-service-v1.respec.mts +2130 -1459
  28. package/src/keystone/keystone-types.mts +50 -2
  29. package/src/keystone/policy/profiles/profile-domain.json +3 -62
  30. package/src/keystone/policy/profiles/profile-sync.json +2 -16
  31. package/src/keystone/policy/schemas/keystone.high.schema.json +1 -1
  32. 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 strategy = KeystoneStrategyFactory.create({ config });
63
- const poolSecret = await strategy.derivePoolSecret({ masterSecret });
64
- const challenges: { [id: string]: any } = {};
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) { data.frameDetails = 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
- poolId: POOL_ID_REVOKE // Explicitly require the special revoke pool
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
- // Scope creates a cryptographic commitment to WHICH pools are being added
729
- scope: JSON.stringify({ add: newPools.map(p => p.id) })
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
  }