@interop/wallet-core 0.62.0 → 0.66.0
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/README.md +41 -16
- package/dist/clientAnnex/credentialAnchoredGenesis.d.ts +46 -26
- package/dist/clientAnnex/credentialAnchoredGenesis.d.ts.map +1 -1
- package/dist/clientAnnex/credentialAnchoredGenesis.js +91 -39
- package/dist/clientAnnex/credentialAnchoredGenesis.js.map +1 -1
- package/dist/clientAnnex/establish.d.ts +119 -35
- package/dist/clientAnnex/establish.d.ts.map +1 -1
- package/dist/clientAnnex/establish.js +227 -160
- package/dist/clientAnnex/establish.js.map +1 -1
- package/dist/clientAnnex/forget.d.ts +18 -8
- package/dist/clientAnnex/forget.d.ts.map +1 -1
- package/dist/clientAnnex/forget.js +37 -64
- package/dist/clientAnnex/forget.js.map +1 -1
- package/dist/clientAnnex/forgetLast.d.ts +85 -38
- package/dist/clientAnnex/forgetLast.d.ts.map +1 -1
- package/dist/clientAnnex/forgetLast.js +155 -98
- package/dist/clientAnnex/forgetLast.js.map +1 -1
- package/dist/clientAnnex/gc.d.ts +3 -2
- package/dist/clientAnnex/gc.d.ts.map +1 -1
- package/dist/clientAnnex/gc.js +16 -7
- package/dist/clientAnnex/gc.js.map +1 -1
- package/dist/clientAnnex/heal.d.ts +65 -14
- package/dist/clientAnnex/heal.d.ts.map +1 -1
- package/dist/clientAnnex/heal.js +397 -298
- package/dist/clientAnnex/heal.js.map +1 -1
- package/dist/clientAnnex/index.d.ts +23 -7
- package/dist/clientAnnex/index.d.ts.map +1 -1
- package/dist/clientAnnex/index.js +26 -7
- package/dist/clientAnnex/index.js.map +1 -1
- package/dist/clientAnnex/ladder.d.ts +58 -0
- package/dist/clientAnnex/ladder.d.ts.map +1 -1
- package/dist/clientAnnex/ladder.js +249 -41
- package/dist/clientAnnex/ladder.js.map +1 -1
- package/dist/clientAnnex/ladderAnchored.d.ts +148 -22
- package/dist/clientAnnex/ladderAnchored.d.ts.map +1 -1
- package/dist/clientAnnex/ladderAnchored.js +391 -346
- package/dist/clientAnnex/ladderAnchored.js.map +1 -1
- package/dist/clientAnnex/log.d.ts +237 -53
- package/dist/clientAnnex/log.d.ts.map +1 -1
- package/dist/clientAnnex/log.js +533 -198
- package/dist/clientAnnex/log.js.map +1 -1
- package/dist/clientAnnex/mend.d.ts +12 -6
- package/dist/clientAnnex/mend.d.ts.map +1 -1
- package/dist/clientAnnex/mend.js +44 -72
- package/dist/clientAnnex/mend.js.map +1 -1
- package/dist/clientAnnex/recoveryLadderAnchored.d.ts +6 -4
- package/dist/clientAnnex/recoveryLadderAnchored.d.ts.map +1 -1
- package/dist/clientAnnex/recoveryLadderAnchored.js +56 -35
- package/dist/clientAnnex/recoveryLadderAnchored.js.map +1 -1
- package/dist/clientAnnex/spaceCapability.d.ts +123 -0
- package/dist/clientAnnex/spaceCapability.d.ts.map +1 -0
- package/dist/clientAnnex/spaceCapability.js +152 -0
- package/dist/clientAnnex/spaceCapability.js.map +1 -0
- package/dist/clientAnnex/stages.d.ts +63 -0
- package/dist/clientAnnex/stages.d.ts.map +1 -0
- package/dist/clientAnnex/stages.js +64 -0
- package/dist/clientAnnex/stages.js.map +1 -0
- package/dist/clientAnnex/zcap.d.ts +1 -1
- package/dist/clientAnnex/zcap.d.ts.map +1 -1
- package/dist/clientAnnex/zcap.js +42 -35
- package/dist/clientAnnex/zcap.js.map +1 -1
- package/dist/clients/policy.d.ts +15 -1
- package/dist/clients/policy.d.ts.map +1 -1
- package/dist/clients/policy.js +12 -6
- package/dist/clients/policy.js.map +1 -1
- package/dist/clients/revocation.d.ts +18 -10
- package/dist/clients/revocation.d.ts.map +1 -1
- package/dist/clients/revocation.js +13 -5
- package/dist/clients/revocation.js.map +1 -1
- package/dist/clients/rosterPolicy.d.ts +10 -2
- package/dist/clients/rosterPolicy.d.ts.map +1 -1
- package/dist/clients/rosterPolicy.js +41 -24
- package/dist/clients/rosterPolicy.js.map +1 -1
- package/dist/descriptors/acquire.d.ts.map +1 -1
- package/dist/descriptors/acquire.js +6 -23
- package/dist/descriptors/acquire.js.map +1 -1
- package/dist/descriptors/cipher.d.ts.map +1 -1
- package/dist/descriptors/cipher.js +5 -0
- package/dist/descriptors/cipher.js.map +1 -1
- package/dist/descriptors/errors.d.ts +43 -0
- package/dist/descriptors/errors.d.ts.map +1 -0
- package/dist/descriptors/errors.js +45 -0
- package/dist/descriptors/errors.js.map +1 -0
- package/dist/descriptors/index.d.ts +5 -0
- package/dist/descriptors/index.d.ts.map +1 -1
- package/dist/descriptors/index.js +5 -0
- package/dist/descriptors/index.js.map +1 -1
- package/dist/enrollment/enrollment.d.ts +37 -10
- package/dist/enrollment/enrollment.d.ts.map +1 -1
- package/dist/enrollment/enrollment.js +52 -15
- package/dist/enrollment/enrollment.js.map +1 -1
- package/dist/genesis/accountGenesis.d.ts +42 -11
- package/dist/genesis/accountGenesis.d.ts.map +1 -1
- package/dist/genesis/accountGenesis.js +80 -22
- package/dist/genesis/accountGenesis.js.map +1 -1
- package/dist/genesis/index.d.ts +3 -1
- package/dist/genesis/index.d.ts.map +1 -1
- package/dist/genesis/index.js +3 -1
- package/dist/genesis/index.js.map +1 -1
- package/dist/identity/agents.d.ts +17 -1
- package/dist/identity/agents.d.ts.map +1 -1
- package/dist/identity/agents.js +23 -7
- package/dist/identity/agents.js.map +1 -1
- package/dist/identity/index.d.ts +3 -1
- package/dist/identity/index.d.ts.map +1 -1
- package/dist/identity/index.js +3 -1
- package/dist/identity/index.js.map +1 -1
- package/dist/index.d.ts +6 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -3
- package/dist/index.js.map +1 -1
- package/dist/keyring/index.d.ts +2 -1
- package/dist/keyring/index.d.ts.map +1 -1
- package/dist/keyring/index.js +2 -1
- package/dist/keyring/index.js.map +1 -1
- package/dist/keyring/record.d.ts.map +1 -1
- package/dist/keyring/record.js +3 -6
- package/dist/keyring/record.js.map +1 -1
- package/dist/keyring/unlockSpace.d.ts +14 -5
- package/dist/keyring/unlockSpace.d.ts.map +1 -1
- package/dist/keyring/unlockSpace.js +31 -36
- package/dist/keyring/unlockSpace.js.map +1 -1
- package/dist/keys/index.d.ts +22 -8
- package/dist/keys/index.d.ts.map +1 -1
- package/dist/keys/index.js +20 -6
- package/dist/keys/index.js.map +1 -1
- package/dist/keys/rosterLogStore.d.ts +15 -12
- package/dist/keys/rosterLogStore.d.ts.map +1 -1
- package/dist/keys/rosterLogStore.js +21 -16
- package/dist/keys/rosterLogStore.js.map +1 -1
- package/dist/keys/rosterStore.d.ts +3 -6
- package/dist/keys/rosterStore.d.ts.map +1 -1
- package/dist/keys/rosterStore.js +10 -9
- package/dist/keys/rosterStore.js.map +1 -1
- package/dist/keys/spaceEpochs.d.ts.map +1 -1
- package/dist/keys/spaceEpochs.js +2 -3
- package/dist/keys/spaceEpochs.js.map +1 -1
- package/dist/keys/userKeyCascade.d.ts +8 -5
- package/dist/keys/userKeyCascade.d.ts.map +1 -1
- package/dist/keys/userKeyCascade.js +7 -6
- package/dist/keys/userKeyCascade.js.map +1 -1
- package/dist/keys/userKeyRoster.d.ts +95 -8
- package/dist/keys/userKeyRoster.d.ts.map +1 -1
- package/dist/keys/userKeyRoster.js +278 -35
- package/dist/keys/userKeyRoster.js.map +1 -1
- package/dist/keys/userKeyRosterCascade.d.ts +117 -16
- package/dist/keys/userKeyRosterCascade.d.ts.map +1 -1
- package/dist/keys/userKeyRosterCascade.js +184 -43
- package/dist/keys/userKeyRosterCascade.js.map +1 -1
- package/dist/keys/wasLabelsStore.d.ts +9 -2
- package/dist/keys/wasLabelsStore.d.ts.map +1 -1
- package/dist/keys/wasLabelsStore.js +14 -6
- package/dist/keys/wasLabelsStore.js.map +1 -1
- package/dist/log.d.ts +7 -2
- package/dist/log.d.ts.map +1 -1
- package/dist/log.js +6 -1
- package/dist/log.js.map +1 -1
- package/dist/recovery/index.d.ts +13 -9
- package/dist/recovery/index.d.ts.map +1 -1
- package/dist/recovery/index.js +12 -8
- package/dist/recovery/index.js.map +1 -1
- package/dist/recovery/recoveryCode.d.ts +27 -7
- package/dist/recovery/recoveryCode.d.ts.map +1 -1
- package/dist/recovery/recoveryCode.js +18 -6
- package/dist/recovery/recoveryCode.js.map +1 -1
- package/dist/recovery/recoveryDelegation.d.ts +23 -0
- package/dist/recovery/recoveryDelegation.d.ts.map +1 -1
- package/dist/recovery/recoveryDelegation.js +66 -46
- package/dist/recovery/recoveryDelegation.js.map +1 -1
- package/dist/recovery/recoveryWebvh.d.ts +75 -63
- package/dist/recovery/recoveryWebvh.d.ts.map +1 -1
- package/dist/recovery/recoveryWebvh.js +127 -116
- package/dist/recovery/recoveryWebvh.js.map +1 -1
- package/dist/request/appKey.d.ts +35 -1
- package/dist/request/appKey.d.ts.map +1 -1
- package/dist/request/appKey.js +29 -10
- package/dist/request/appKey.js.map +1 -1
- package/dist/request/classify.d.ts +32 -8
- package/dist/request/classify.d.ts.map +1 -1
- package/dist/request/classify.js +39 -14
- package/dist/request/classify.js.map +1 -1
- package/dist/request/ephemeralExchange.d.ts +1 -6
- package/dist/request/ephemeralExchange.d.ts.map +1 -1
- package/dist/request/ephemeralExchange.js.map +1 -1
- package/dist/request/onboarding.d.ts.map +1 -1
- package/dist/request/onboarding.js +2 -2
- package/dist/request/onboarding.js.map +1 -1
- package/dist/request/parse.d.ts.map +1 -1
- package/dist/request/parse.js +2 -3
- package/dist/request/parse.js.map +1 -1
- package/dist/resourceLog/controller.d.ts +34 -12
- package/dist/resourceLog/controller.d.ts.map +1 -1
- package/dist/resourceLog/controller.js +79 -86
- package/dist/resourceLog/controller.js.map +1 -1
- package/dist/resourceLog/document.d.ts +182 -0
- package/dist/resourceLog/document.d.ts.map +1 -0
- package/dist/resourceLog/document.js +159 -0
- package/dist/resourceLog/document.js.map +1 -0
- package/dist/resourceLog/errors.d.ts +47 -8
- package/dist/resourceLog/errors.d.ts.map +1 -1
- package/dist/resourceLog/errors.js +54 -8
- package/dist/resourceLog/errors.js.map +1 -1
- package/dist/resourceLog/index.d.ts +7 -3
- package/dist/resourceLog/index.d.ts.map +1 -1
- package/dist/resourceLog/index.js +7 -3
- package/dist/resourceLog/index.js.map +1 -1
- package/dist/resourceLog/ladderRungs.d.ts +35 -0
- package/dist/resourceLog/ladderRungs.d.ts.map +1 -0
- package/dist/resourceLog/ladderRungs.js +352 -0
- package/dist/resourceLog/ladderRungs.js.map +1 -0
- package/dist/resourceLog/license.d.ts +42 -17
- package/dist/resourceLog/license.d.ts.map +1 -1
- package/dist/resourceLog/license.js +38 -24
- package/dist/resourceLog/license.js.map +1 -1
- package/dist/space/activity.d.ts +15 -15
- package/dist/space/activity.d.ts.map +1 -1
- package/dist/space/activity.js +15 -15
- package/dist/space/activity.js.map +1 -1
- package/dist/space/collections.d.ts +11 -0
- package/dist/space/collections.d.ts.map +1 -1
- package/dist/space/collections.js +13 -0
- package/dist/space/collections.js.map +1 -1
- package/dist/space/deleteSpace.d.ts +28 -0
- package/dist/space/deleteSpace.d.ts.map +1 -0
- package/dist/space/deleteSpace.js +44 -0
- package/dist/space/deleteSpace.js.map +1 -0
- package/dist/space/errors.d.ts.map +1 -1
- package/dist/space/errors.js +0 -1
- package/dist/space/errors.js.map +1 -1
- package/dist/space/index.d.ts +6 -0
- package/dist/space/index.d.ts.map +1 -1
- package/dist/space/index.js +6 -0
- package/dist/space/index.js.map +1 -1
- package/dist/space/plaintextCollection.d.ts +43 -0
- package/dist/space/plaintextCollection.d.ts.map +1 -0
- package/dist/space/plaintextCollection.js +17 -0
- package/dist/space/plaintextCollection.js.map +1 -0
- package/dist/stages.d.ts +23 -0
- package/dist/stages.d.ts.map +1 -0
- package/dist/stages.js +23 -0
- package/dist/stages.js.map +1 -0
- package/dist/sync/index.d.ts +7 -0
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +7 -0
- package/dist/sync/index.js.map +1 -1
- package/dist/sync/push.js +4 -4
- package/dist/sync/push.js.map +1 -1
- package/dist/sync/remint.js +2 -2
- package/dist/sync/remint.js.map +1 -1
- package/dist/sync/types.d.ts +41 -1
- package/dist/sync/types.d.ts.map +1 -1
- package/dist/sync/types.js +47 -1
- package/dist/sync/types.js.map +1 -1
- package/dist/unlock/index.d.ts +5 -1
- package/dist/unlock/index.d.ts.map +1 -1
- package/dist/unlock/index.js +5 -1
- package/dist/unlock/index.js.map +1 -1
- package/dist/unlock/retire.d.ts +35 -7
- package/dist/unlock/retire.d.ts.map +1 -1
- package/dist/unlock/retire.js +52 -14
- package/dist/unlock/retire.js.map +1 -1
- package/dist/unlock/standingClient.d.ts.map +1 -1
- package/dist/unlock/standingClient.js +5 -1
- package/dist/unlock/standingClient.js.map +1 -1
- package/dist/unlock/standingWebvh.d.ts +217 -35
- package/dist/unlock/standingWebvh.d.ts.map +1 -1
- package/dist/unlock/standingWebvh.js +469 -210
- package/dist/unlock/standingWebvh.js.map +1 -1
- package/dist/webvh/accountEntry.d.ts +146 -0
- package/dist/webvh/accountEntry.d.ts.map +1 -0
- package/dist/webvh/accountEntry.js +239 -0
- package/dist/webvh/accountEntry.js.map +1 -0
- package/dist/webvh/didWeb.d.ts +12 -7
- package/dist/webvh/didWeb.d.ts.map +1 -1
- package/dist/webvh/didWeb.js +2 -2
- package/dist/webvh/didWeb.js.map +1 -1
- package/dist/webvh/didWebProjection.d.ts +164 -0
- package/dist/webvh/didWebProjection.d.ts.map +1 -0
- package/dist/webvh/didWebProjection.js +230 -0
- package/dist/webvh/didWebProjection.js.map +1 -0
- package/dist/webvh/didWebvh.d.ts +275 -136
- package/dist/webvh/didWebvh.d.ts.map +1 -1
- package/dist/webvh/didWebvh.js +307 -340
- package/dist/webvh/didWebvh.js.map +1 -1
- package/dist/webvh/enrollClient.d.ts +65 -0
- package/dist/webvh/enrollClient.d.ts.map +1 -0
- package/dist/webvh/enrollClient.js +172 -0
- package/dist/webvh/enrollClient.js.map +1 -0
- package/dist/webvh/index.d.ts +37 -12
- package/dist/webvh/index.d.ts.map +1 -1
- package/dist/webvh/index.js +33 -10
- package/dist/webvh/index.js.map +1 -1
- package/dist/webvh/listClients.d.ts +1 -35
- package/dist/webvh/listClients.d.ts.map +1 -1
- package/dist/webvh/listClients.js +2 -30
- package/dist/webvh/listClients.js.map +1 -1
- package/dist/webvh/revokeClient.d.ts +41 -11
- package/dist/webvh/revokeClient.d.ts.map +1 -1
- package/dist/webvh/revokeClient.js +99 -48
- package/dist/webvh/revokeClient.js.map +1 -1
- package/dist/webvh/standingZcap.d.ts +61 -14
- package/dist/webvh/standingZcap.d.ts.map +1 -1
- package/dist/webvh/standingZcap.js +91 -0
- package/dist/webvh/standingZcap.js.map +1 -1
- package/dist/webvh/verifyLog.d.ts +59 -5
- package/dist/webvh/verifyLog.d.ts.map +1 -1
- package/dist/webvh/verifyLog.js +76 -11
- package/dist/webvh/verifyLog.js.map +1 -1
- package/dist/webvh/wasIdStore.d.ts +8 -8
- package/dist/webvh/wasIdStore.d.ts.map +1 -1
- package/dist/webvh/wasIdStore.js +39 -14
- package/dist/webvh/wasIdStore.js.map +1 -1
- package/dist/webvh/zcap.d.ts +14 -1
- package/dist/webvh/zcap.d.ts.map +1 -1
- package/dist/webvh/zcap.js +3 -27
- package/dist/webvh/zcap.js.map +1 -1
- package/package.json +2 -2
- package/dist/webvh/keyAgreement.d.ts +0 -97
- package/dist/webvh/keyAgreement.d.ts.map +0 -1
- package/dist/webvh/keyAgreement.js +0 -71
- package/dist/webvh/keyAgreement.js.map +0 -1
package/dist/clientAnnex/log.js
CHANGED
|
@@ -52,11 +52,13 @@ import { createDID, deriveNextKeyHash, updateDID } from '@interop/did-method-web
|
|
|
52
52
|
import { rootCapabilityId, spaceItems, spacePath, toUrl } from '@interop/was-client/paths';
|
|
53
53
|
import { base64urlnopad } from '@scure/base';
|
|
54
54
|
import { DID_LOG_RESOURCE } from '../space/collections.js';
|
|
55
|
+
import { plaintextCollection } from '../space/plaintextCollection.js';
|
|
55
56
|
import { resourceLogPinId } from '@interop/vh-resource-log';
|
|
56
57
|
import { clientAnnexRung } from './ladder.js';
|
|
57
|
-
import { assertCarryOverCommitments, concludeWithPublishedLog, didWebvhControllerTemplate, MULTIKEY_VM_TYPE, pinOfLog,
|
|
58
|
-
import {
|
|
59
|
-
import {
|
|
58
|
+
import { assertCarryOverCommitments, assertPublishedLogDid, concludeWithPublishedLog, currentLogParameters, didWebvhControllerTemplate, MULTIKEY_VM_TYPE, pinOfLog, putLogResource, readPublishedLogOrThrow, updateKeySigner, WebvhLogConflictError, withLogConflictRetry } from '../webvh/didWebvh.js';
|
|
59
|
+
import { signAccountEntry } from '../webvh/accountEntry.js';
|
|
60
|
+
import { relationIds } from '../resourceLog/document.js';
|
|
61
|
+
import { STANDING_ZCAP_TTL_MS, standingZcapStale } from '../webvh/standingZcap.js';
|
|
60
62
|
import { wasWebvhLogStore } from '../webvh/wasIdStore.js';
|
|
61
63
|
/**
|
|
62
64
|
* The Space Description `type` array of the auxiliary annex Space, set at
|
|
@@ -256,24 +258,30 @@ export async function createClientAnnexLog({ wasServerUrl, spaceId, generationId
|
|
|
256
258
|
* @param options.controller {string} the Space controller (the account
|
|
257
259
|
* did:webvh where it exists; a bootstrap did:key on a ladder-anchored signup,
|
|
258
260
|
* promoted the same way the account Space's controller is)
|
|
259
|
-
* @returns {Promise<
|
|
261
|
+
* @returns {Promise<SpaceDescription>} the Space Description this call read
|
|
262
|
+
* (the Space already existed) or wrote (it did not), so a caller flipping
|
|
263
|
+
* the controller straight afterwards can hand it back as `current` instead
|
|
264
|
+
* of paying for a second read.
|
|
260
265
|
*/
|
|
261
266
|
export async function ensureClientAnnexSpace({ was, spaceId, controller }) {
|
|
262
267
|
const space = was.space(spaceId);
|
|
263
268
|
const current = await space.describe();
|
|
264
269
|
if (current === null) {
|
|
265
|
-
|
|
270
|
+
// `current: null` is the answer this read just produced, so was-client
|
|
271
|
+
// skips its own pre-merge describe rather than repeating it.
|
|
272
|
+
return space.configure({
|
|
273
|
+
current: null,
|
|
266
274
|
controller,
|
|
267
275
|
type: CLIENT_ANNEX_SPACE_TYPE,
|
|
268
276
|
force: true
|
|
269
277
|
});
|
|
270
|
-
return;
|
|
271
278
|
}
|
|
272
279
|
if (!current.type?.includes(DELEGATED_CLIENTS_SPACE_TYPE)) {
|
|
273
280
|
throw new Error(`The Space "${spaceId}" exists but is not typed as the ` +
|
|
274
281
|
'delegated-clients auxiliary Space; its type is immutable, so it ' +
|
|
275
282
|
'cannot hold client-annex generations.');
|
|
276
283
|
}
|
|
284
|
+
return current;
|
|
277
285
|
}
|
|
278
286
|
/**
|
|
279
287
|
* Mints a fresh annex generation with controller-tier signing: ensures
|
|
@@ -307,13 +315,23 @@ export async function ensureClientAnnexSpace({ was, spaceId, controller }) {
|
|
|
307
315
|
* @param options.nextKeyHashes {string[]} every standing credential's
|
|
308
316
|
* rung-0 hash, the minting credential's included
|
|
309
317
|
* @param options.signer {Signer} the minting credential's rung-0 signer
|
|
310
|
-
* @returns {Promise<
|
|
311
|
-
*
|
|
318
|
+
* @returns {Promise<PublishedWebvhLog & { generationId: string;
|
|
319
|
+
* spaceDescription?: SpaceDescription }>} the published head of the
|
|
320
|
+
* genesis log, which a stage building the generation's next entry can
|
|
321
|
+
* stand on instead of re-reading it, carrying the PUT's own ETag when the
|
|
322
|
+
* store handed one back. `spaceDescription` is the auxiliary Space's
|
|
323
|
+
* Description as the ensure read or wrote it, so a caller flipping the
|
|
324
|
+
* controller afterwards need not re-read it. Always present here, since
|
|
325
|
+
* this minter always runs the ensure.
|
|
312
326
|
*/
|
|
313
327
|
export async function mintClientAnnexGeneration({ was, wasServerUrl, spaceId, controller, updateKeyPublicKeyMultibase, nextKeyHashes, signer }) {
|
|
314
|
-
await ensureClientAnnexSpace({
|
|
328
|
+
const spaceDescription = await ensureClientAnnexSpace({
|
|
329
|
+
was,
|
|
330
|
+
spaceId,
|
|
331
|
+
controller
|
|
332
|
+
});
|
|
315
333
|
const generationId = mintGenerationId();
|
|
316
|
-
|
|
334
|
+
const published = await publishClientAnnexGenesis({
|
|
317
335
|
was,
|
|
318
336
|
wasServerUrl,
|
|
319
337
|
spaceId,
|
|
@@ -322,6 +340,7 @@ export async function mintClientAnnexGeneration({ was, wasServerUrl, spaceId, co
|
|
|
322
340
|
nextKeyHashes,
|
|
323
341
|
signer
|
|
324
342
|
});
|
|
343
|
+
return { ...published, spaceDescription };
|
|
325
344
|
}
|
|
326
345
|
/**
|
|
327
346
|
* The shared genesis-publish tail of both generation minters: creates the
|
|
@@ -338,19 +357,29 @@ export async function mintClientAnnexGeneration({ was, wasServerUrl, spaceId, co
|
|
|
338
357
|
* @param options.signer {Signer}
|
|
339
358
|
* @param [options.capability] {IZcap} an invocation capability the
|
|
340
359
|
* collection create and the genesis publish ride (a delegated minter)
|
|
341
|
-
* @returns {Promise<
|
|
342
|
-
*
|
|
360
|
+
* @returns {Promise<PublishedWebvhLog & { generationId: string }>} the
|
|
361
|
+
* published head of the log this call just wrote -- the genesis log, its
|
|
362
|
+
* DID and document, the effective update-key parameters, and the PUT's own
|
|
363
|
+
* ETag when the store handed one back. It is what a stage building the
|
|
364
|
+
* generation's next entry can stand on instead of re-reading the log
|
|
365
|
+
* (see {@link ensureGenerationDelegationCurrent}'s `published`); the ETag
|
|
366
|
+
* is absent against a backend that serves none, where a caller must read
|
|
367
|
+
* for itself rather than degrade its compare-and-swap to an unconditional
|
|
368
|
+
* write.
|
|
343
369
|
*/
|
|
344
370
|
async function publishClientAnnexGenesis({ was, wasServerUrl, spaceId, generationId, updateKeyPublicKeyMultibase, nextKeyHashes, signer, capability }) {
|
|
345
371
|
// The generation collection must exist before its first resource PUT; a
|
|
346
|
-
// fresh random generation id means this is always a create
|
|
347
|
-
//
|
|
348
|
-
//
|
|
349
|
-
// collection is capability-gated rather
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
372
|
+
// fresh random generation id means this is always a create, so the truthful
|
|
373
|
+
// `current: null` skips was-client's pre-merge describe of a Collection
|
|
374
|
+
// that cannot exist. Plaintext on purpose: the server resolves the annex
|
|
375
|
+
// DID out of its own storage, and the collection is capability-gated rather
|
|
376
|
+
// than encrypted.
|
|
377
|
+
await plaintextCollection({
|
|
378
|
+
was,
|
|
379
|
+
spaceId,
|
|
380
|
+
collectionId: generationId,
|
|
381
|
+
capability
|
|
382
|
+
}).configure({ current: null, name: generationId, force: true });
|
|
354
383
|
const created = await createClientAnnexLog({
|
|
355
384
|
wasServerUrl,
|
|
356
385
|
spaceId,
|
|
@@ -359,7 +388,7 @@ async function publishClientAnnexGenesis({ was, wasServerUrl, spaceId, generatio
|
|
|
359
388
|
nextKeyHashes,
|
|
360
389
|
signer
|
|
361
390
|
});
|
|
362
|
-
await putLogResource({
|
|
391
|
+
const written = await putLogResource({
|
|
363
392
|
store: clientAnnexLogStore({
|
|
364
393
|
was,
|
|
365
394
|
spaceId,
|
|
@@ -369,7 +398,12 @@ async function publishClientAnnexGenesis({ was, wasServerUrl, spaceId, generatio
|
|
|
369
398
|
log: created.log,
|
|
370
399
|
ifNoneMatch: true
|
|
371
400
|
});
|
|
372
|
-
return {
|
|
401
|
+
return {
|
|
402
|
+
...created,
|
|
403
|
+
generationId,
|
|
404
|
+
...currentLogParameters(created),
|
|
405
|
+
...(written.etag !== undefined ? { etag: written.etag } : {})
|
|
406
|
+
};
|
|
373
407
|
}
|
|
374
408
|
/**
|
|
375
409
|
* Mints a fresh annex generation signed by a standing CREDENTIAL's
|
|
@@ -403,16 +437,22 @@ async function publishClientAnnexGenesis({ was, wasServerUrl, spaceId, generatio
|
|
|
403
437
|
* subtree). The typed-Space ensure is then skipped: the delegation's target
|
|
404
438
|
* covers the collections beneath the Space, never the Space Description,
|
|
405
439
|
* and a standing sibling delegation presupposes the auxiliary Space
|
|
406
|
-
* @returns {Promise<
|
|
407
|
-
*
|
|
440
|
+
* @returns {Promise<PublishedWebvhLog & { generationId: string;
|
|
441
|
+
* spaceDescription?: SpaceDescription }>} the published head of the
|
|
442
|
+
* genesis log, which a stage building the generation's next entry can
|
|
443
|
+
* stand on instead of re-reading it, carrying the PUT's own ETag when the
|
|
444
|
+
* store handed one back. `spaceDescription` is the auxiliary Space's
|
|
445
|
+
* Description as the ensure read or wrote it, so a caller flipping the
|
|
446
|
+
* controller afterwards need not re-read it. Present exactly when the
|
|
447
|
+
* ensure ran, so absent under a supplied `capability`.
|
|
408
448
|
*/
|
|
409
449
|
export async function mintCredentialClientAnnexGeneration({ was, wasServerUrl, spaceId, controller, ladderSeed, extraNextKeyHashes = [], capability }) {
|
|
410
|
-
|
|
411
|
-
await ensureClientAnnexSpace({ was, spaceId, controller })
|
|
412
|
-
|
|
450
|
+
const spaceDescription = capability === undefined
|
|
451
|
+
? await ensureClientAnnexSpace({ was, spaceId, controller })
|
|
452
|
+
: undefined;
|
|
413
453
|
const generationId = mintGenerationId();
|
|
414
454
|
const rung = await clientAnnexRung({ ladderSeed, generationId });
|
|
415
|
-
|
|
455
|
+
const published = await publishClientAnnexGenesis({
|
|
416
456
|
was,
|
|
417
457
|
wasServerUrl,
|
|
418
458
|
spaceId,
|
|
@@ -425,6 +465,10 @@ export async function mintCredentialClientAnnexGeneration({ was, wasServerUrl, s
|
|
|
425
465
|
signer: await updateKeySigner({ seed: rung.seed }),
|
|
426
466
|
...(capability !== undefined ? { capability } : {})
|
|
427
467
|
});
|
|
468
|
+
return {
|
|
469
|
+
...published,
|
|
470
|
+
...(spaceDescription !== undefined ? { spaceDescription } : {})
|
|
471
|
+
};
|
|
428
472
|
}
|
|
429
473
|
/**
|
|
430
474
|
* The type IRI of the account document's delegated-clients service entry --
|
|
@@ -481,6 +525,61 @@ export function delegatedClientsPointer({ doc }) {
|
|
|
481
525
|
}
|
|
482
526
|
return undefined;
|
|
483
527
|
}
|
|
528
|
+
/**
|
|
529
|
+
* Every auxiliary annex Space the account log's `#DelegatedClients` pointer
|
|
530
|
+
* has ever named, oldest first and one entry per Space. A pointer entry is
|
|
531
|
+
* append-only: a superseded value stops being current but its Space survives
|
|
532
|
+
* the move, so an enumeration that reads only the resolved document (the
|
|
533
|
+
* {@link delegatedClientsPointer} case) misses every Space a generation swap
|
|
534
|
+
* has left behind. Several generations of one Space collapse to the entry
|
|
535
|
+
* that named it first.
|
|
536
|
+
*
|
|
537
|
+
* The account deletion ceremony is the reader: it must name each auxiliary
|
|
538
|
+
* Space it is about to destroy, and the log is the only durable record of
|
|
539
|
+
* the superseded ones. Each entry carries the annex DID and its host beside
|
|
540
|
+
* the Space id, because an account that has migrated hosts leaves entries
|
|
541
|
+
* this deployment cannot address: deleting `spaceId` on the CURRENT host
|
|
542
|
+
* would address a Space that is not the one the entry names, and a 404 there
|
|
543
|
+
* would otherwise read as a clean deletion. Such an entry is the caller's to
|
|
544
|
+
* report as a residue.
|
|
545
|
+
*
|
|
546
|
+
* The acting unlock record's own `delegatedClients` sibling target is NOT
|
|
547
|
+
* included here; a caller that wants it unions it in itself, since a torn
|
|
548
|
+
* establishment can converge on a Space no pointer entry ever named.
|
|
549
|
+
*
|
|
550
|
+
* An entry carrying no document state, and an endpoint that does not parse
|
|
551
|
+
* as a client-annex DID, are skipped rather than refused: the walk is an
|
|
552
|
+
* enumeration aid, and a caller cannot act on an id it could not read.
|
|
553
|
+
*
|
|
554
|
+
* @param options {object}
|
|
555
|
+
* @param options.log {DIDLog} the VERIFIED account log
|
|
556
|
+
* @returns {Array<{ did: string, host: string, spaceId: string }>} the
|
|
557
|
+
* Spaces, in log order
|
|
558
|
+
*/
|
|
559
|
+
export function delegatedClientsSpaceHistory({ log }) {
|
|
560
|
+
const spaces = [];
|
|
561
|
+
for (const entry of log) {
|
|
562
|
+
if (entry?.state === undefined || entry.state === null) {
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
565
|
+
const pointed = delegatedClientsPointer({ doc: entry.state });
|
|
566
|
+
if (pointed === undefined) {
|
|
567
|
+
continue;
|
|
568
|
+
}
|
|
569
|
+
let parts;
|
|
570
|
+
try {
|
|
571
|
+
parts = clientAnnexDidParts({ did: pointed });
|
|
572
|
+
}
|
|
573
|
+
catch {
|
|
574
|
+
continue;
|
|
575
|
+
}
|
|
576
|
+
if (spaces.some(space => space.spaceId === parts.spaceId)) {
|
|
577
|
+
continue;
|
|
578
|
+
}
|
|
579
|
+
spaces.push({ did: pointed, host: parts.host, spaceId: parts.spaceId });
|
|
580
|
+
}
|
|
581
|
+
return spaces;
|
|
582
|
+
}
|
|
484
583
|
/**
|
|
485
584
|
* The account document's `service` array with the delegated-clients pointer
|
|
486
585
|
* set to `clientAnnexDid`. An existing pointer entry is re-pointed in place,
|
|
@@ -901,33 +1000,42 @@ function withGenerationDelegationEntry({ doc, clientAnnexDid, delegation }) {
|
|
|
901
1000
|
];
|
|
902
1001
|
}
|
|
903
1002
|
/**
|
|
904
|
-
* Parses the auxiliary Space id and generation id out of an
|
|
905
|
-
* string.
|
|
1003
|
+
* Parses the host, the auxiliary Space id and the generation id out of an
|
|
1004
|
+
* annex DID string. All three are permanent substrings of every annex DID by
|
|
906
1005
|
* construction: the generation id is the final path segment of the annex
|
|
907
1006
|
* DID (`did:webvh:<scid>:<host>:...:space:<spaceId>:<generationId>`), and it
|
|
908
1007
|
* is the generation-identifying half of the annex rung HKDF
|
|
909
1008
|
* labels, so this parse is what lets an enrollee derive its writing key from
|
|
910
1009
|
* the pointer alone -- no log read, no registry.
|
|
911
1010
|
*
|
|
1011
|
+
* The host is the method-specific id's second segment, percent-decoded (a
|
|
1012
|
+
* port rides as `%3A` inside the one segment). A caller enumerating Spaces
|
|
1013
|
+
* out of a log compares it against the deployment it is talking to: an
|
|
1014
|
+
* account that has migrated hosts carries entries naming the old one, which
|
|
1015
|
+
* this deployment cannot address.
|
|
1016
|
+
*
|
|
912
1017
|
* @param options {object}
|
|
913
1018
|
* @param options.did {string} an annex did:webvh string
|
|
914
|
-
* @returns {{ spaceId: string, generationId: string }}
|
|
1019
|
+
* @returns {{ host: string, spaceId: string, generationId: string }}
|
|
915
1020
|
*/
|
|
916
1021
|
export function clientAnnexDidParts({ did }) {
|
|
917
1022
|
const parts = did.split(':');
|
|
918
1023
|
const generationId = parts[parts.length - 1];
|
|
919
1024
|
const spaceId = parts[parts.length - 2];
|
|
1025
|
+
const host = parts[3];
|
|
920
1026
|
if (parts.length < 7 ||
|
|
921
1027
|
parts[0] !== 'did' ||
|
|
922
1028
|
parts[1] !== 'webvh' ||
|
|
923
1029
|
parts[parts.length - 3] !== 'space' ||
|
|
924
1030
|
generationId === undefined ||
|
|
925
1031
|
spaceId === undefined ||
|
|
926
|
-
spaceId.length === 0
|
|
1032
|
+
spaceId.length === 0 ||
|
|
1033
|
+
host === undefined ||
|
|
1034
|
+
host.length === 0) {
|
|
927
1035
|
throw new Error(`Not a client annex did:webvh: "${did}".`);
|
|
928
1036
|
}
|
|
929
1037
|
assertGenerationId(generationId);
|
|
930
|
-
return { spaceId, generationId };
|
|
1038
|
+
return { host: decodeURIComponent(host), spaceId, generationId };
|
|
931
1039
|
}
|
|
932
1040
|
/**
|
|
933
1041
|
* Thrown when the published annex log commits neither the writing
|
|
@@ -944,6 +1052,37 @@ export class ClientAnnexRungUncommittedError extends Error {
|
|
|
944
1052
|
this.name = 'ClientAnnexRungUncommittedError';
|
|
945
1053
|
}
|
|
946
1054
|
}
|
|
1055
|
+
/**
|
|
1056
|
+
* Derives a generation's rung-0 key for a ladder and admits it as the writer
|
|
1057
|
+
* of the entry about to be built: it must stand revealed in the log's
|
|
1058
|
+
* `updateKeys`, or its hash must stand committed in `nextKeyHashes`.
|
|
1059
|
+
*
|
|
1060
|
+
* The one implementation of the annex's writer-admission rule, which every
|
|
1061
|
+
* entry builder in this module applies before it mints anything. Annex
|
|
1062
|
+
* entries verify against the log's own hash-commitment chain, so a key that
|
|
1063
|
+
* is neither revealed nor committed can never be made to verify mid-log --
|
|
1064
|
+
* the caller supplies only the refusal message, since what a locked-out
|
|
1065
|
+
* credential should do next differs per ceremony.
|
|
1066
|
+
*
|
|
1067
|
+
* @param options {object}
|
|
1068
|
+
* @param options.ladderSeed {Uint8Array} the acting credential's ladder seed
|
|
1069
|
+
* @param options.generationId {string}
|
|
1070
|
+
* @param options.updateKeys {string[]} the keys the entry will be verified
|
|
1071
|
+
* against (the retired members already excluded, where a ceremony excludes
|
|
1072
|
+
* them)
|
|
1073
|
+
* @param options.nextKeyHashes {string[]} likewise
|
|
1074
|
+
* @param options.message {string} the refusal's message
|
|
1075
|
+
* @returns {Promise<{ seed: Uint8Array; keyMultibase: string }>}
|
|
1076
|
+
*/
|
|
1077
|
+
async function admitActingRung({ ladderSeed, generationId, updateKeys, nextKeyHashes, message }) {
|
|
1078
|
+
const rung = await clientAnnexRung({ ladderSeed, generationId });
|
|
1079
|
+
const rungHash = await deriveNextKeyHash(rung.keyMultibase);
|
|
1080
|
+
if (!updateKeys.includes(rung.keyMultibase) &&
|
|
1081
|
+
!nextKeyHashes.includes(rungHash)) {
|
|
1082
|
+
throw new ClientAnnexRungUncommittedError(message);
|
|
1083
|
+
}
|
|
1084
|
+
return rung;
|
|
1085
|
+
}
|
|
947
1086
|
/**
|
|
948
1087
|
* Reads and resolves the published annex log through the narrow seam, or
|
|
949
1088
|
* throws when the generation's `did.jsonl` is missing (an unpointed or
|
|
@@ -957,18 +1096,14 @@ export class ClientAnnexRungUncommittedError extends Error {
|
|
|
957
1096
|
* @returns {Promise<PublishedWebvhLog>}
|
|
958
1097
|
*/
|
|
959
1098
|
async function readClientAnnexLogOrThrow({ store, expectedDid, pinStore, logId }) {
|
|
960
|
-
|
|
961
|
-
const published = await readPublishedLog({
|
|
1099
|
+
return readPublishedLogOrThrow({
|
|
962
1100
|
idStore: store,
|
|
963
1101
|
...(expectedDid !== undefined ? { expectedDid } : {}),
|
|
964
1102
|
...(pinStore !== undefined ? { pinStore } : {}),
|
|
965
|
-
...(logId !== undefined ? { logId } : {})
|
|
1103
|
+
...(logId !== undefined ? { logId } : {}),
|
|
1104
|
+
missingMessage: 'client annex: did.jsonl is missing; the generation was never minted ' +
|
|
1105
|
+
'or has been collected.'
|
|
966
1106
|
});
|
|
967
|
-
if (!published) {
|
|
968
|
-
throw new Error('client annex: did.jsonl is missing; the generation was never minted or ' +
|
|
969
|
-
'has been collected.');
|
|
970
|
-
}
|
|
971
|
-
return published;
|
|
972
1107
|
}
|
|
973
1108
|
/**
|
|
974
1109
|
* TRANSIENT ENROLLMENT: publishes one per-visit verification method into a
|
|
@@ -1032,27 +1167,56 @@ async function readClientAnnexLogOrThrow({ store, expectedDid, pinStore, logId }
|
|
|
1032
1167
|
* transient session passes an in-memory store)
|
|
1033
1168
|
* @param [options.logId] {string} the generation's pin-slot key, from
|
|
1034
1169
|
* {@link clientAnnexLogPinId}; required whenever a `pinStore` is supplied
|
|
1170
|
+
* @param [options.published] {PublishedWebvhLog} a head the caller already
|
|
1171
|
+
* read and verified under this same pin slot, so the enrollment builds its
|
|
1172
|
+
* entry on it instead of spending a second round trip on the same log (the
|
|
1173
|
+
* transient visit's one-read composition). The FIRST attempt alone rides it:
|
|
1174
|
+
* a lost compare-and-swap means the head is stale by definition, so the
|
|
1175
|
+
* conflict retry re-reads under the pin. That threaded attempt is EXTRA
|
|
1176
|
+
* rather than one of the retry's three: a caller who saved a read is left
|
|
1177
|
+
* with the same conflict budget as one who did not
|
|
1035
1178
|
* @returns {Promise<{ did: string, doc: DIDDoc, log: DIDLog }>}
|
|
1036
1179
|
*/
|
|
1037
|
-
export async function enrollClientAnnexTransientClient(
|
|
1038
|
-
|
|
1180
|
+
export async function enrollClientAnnexTransientClient({ published: threadedHead, ...rest }) {
|
|
1181
|
+
if (threadedHead !== undefined) {
|
|
1182
|
+
try {
|
|
1183
|
+
return await enrollClientAnnexTransientClientOnce({
|
|
1184
|
+
...rest,
|
|
1185
|
+
published: threadedHead
|
|
1186
|
+
});
|
|
1187
|
+
}
|
|
1188
|
+
catch (err) {
|
|
1189
|
+
// A lost compare-and-swap on the threaded head says only that the head
|
|
1190
|
+
// is stale; the retry below re-reads under the pin with its whole
|
|
1191
|
+
// budget. Every other failure is the caller's.
|
|
1192
|
+
if (!(err instanceof WebvhLogConflictError)) {
|
|
1193
|
+
throw err;
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
return withLogConflictRetry(() => enrollClientAnnexTransientClientOnce(rest));
|
|
1039
1198
|
}
|
|
1040
1199
|
/**
|
|
1041
1200
|
* One attempt of {@link enrollClientAnnexTransientClient}, re-invoked by the
|
|
1042
1201
|
* conflict retry (with the same signing key -- static rung 0 has no
|
|
1043
|
-
* advanced-rung retry shape).
|
|
1202
|
+
* advanced-rung retry shape) and with no threaded head after the first.
|
|
1044
1203
|
*
|
|
1045
1204
|
* @param options {object} see {@link enrollClientAnnexTransientClient}
|
|
1046
1205
|
* @returns {Promise<{ did: string, doc: DIDDoc, log: DIDLog }>}
|
|
1047
1206
|
*/
|
|
1048
|
-
async function enrollClientAnnexTransientClientOnce({ store, ladderSeed, generationId, transientKeyMultibase, services, mintGenerationDelegation: mintDelegation, expectedDid, pinStore, logId }) {
|
|
1207
|
+
async function enrollClientAnnexTransientClientOnce({ store, ladderSeed, generationId, transientKeyMultibase, services, mintGenerationDelegation: mintDelegation, expectedDid, pinStore, logId, published: alreadyRead }) {
|
|
1049
1208
|
assertGenerationId(generationId);
|
|
1050
|
-
const published =
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1209
|
+
const published = alreadyRead !== undefined
|
|
1210
|
+
? assertPublishedLogDid({
|
|
1211
|
+
published: alreadyRead,
|
|
1212
|
+
...(expectedDid !== undefined ? { expectedDid } : {})
|
|
1213
|
+
})
|
|
1214
|
+
: await readClientAnnexLogOrThrow({
|
|
1215
|
+
store,
|
|
1216
|
+
...(expectedDid !== undefined ? { expectedDid } : {}),
|
|
1217
|
+
...(pinStore !== undefined ? { pinStore } : {}),
|
|
1218
|
+
...(logId !== undefined ? { logId } : {})
|
|
1219
|
+
});
|
|
1056
1220
|
const { did, doc } = published;
|
|
1057
1221
|
const vmId = `${did}#${transientKeyMultibase}`;
|
|
1058
1222
|
// Already enrolled (a completed earlier run): the VM and everything the
|
|
@@ -1062,14 +1226,15 @@ async function enrollClientAnnexTransientClientOnce({ store, ladderSeed, generat
|
|
|
1062
1226
|
if (existingMethods.some(method => method.id === vmId)) {
|
|
1063
1227
|
return { did, doc, log: published.log };
|
|
1064
1228
|
}
|
|
1065
|
-
const rung = await
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1229
|
+
const rung = await admitActingRung({
|
|
1230
|
+
ladderSeed,
|
|
1231
|
+
generationId,
|
|
1232
|
+
updateKeys: published.updateKeys,
|
|
1233
|
+
nextKeyHashes: published.nextKeyHashes,
|
|
1234
|
+
message: "client annex: the log commits neither this credential's rung-0 key nor " +
|
|
1070
1235
|
'its hash; a credential bound mid-generation cannot write the ' +
|
|
1071
|
-
'annex until a writer commits its hash or the next GC swap does.'
|
|
1072
|
-
}
|
|
1236
|
+
'annex until a writer commits its hash or the next GC swap does.'
|
|
1237
|
+
});
|
|
1073
1238
|
// A non-rotating entry re-states `updateKeys`, which the resolver checks
|
|
1074
1239
|
// against the previous entry's commitments -- genesis enforces the
|
|
1075
1240
|
// carry-over convention, and this refuses a log that lost it anyway.
|
|
@@ -1125,6 +1290,13 @@ async function enrollClientAnnexTransientClientOnce({ store, ladderSeed, generat
|
|
|
1125
1290
|
// The log only -- an annex has no did:web projection -- conditional on
|
|
1126
1291
|
// the read this entry was built on.
|
|
1127
1292
|
await putLogResource({ store, log: updated.log, ifMatch: published.etag });
|
|
1293
|
+
// Advance the pin to what this entry just published, so a host serving the
|
|
1294
|
+
// pre-entry log straight afterwards is refused as a rollback on the next
|
|
1295
|
+
// read (equal-to-pin would otherwise be accepted, and a later stage built on
|
|
1296
|
+
// the stale head would miss this entry).
|
|
1297
|
+
if (pinStore && logId !== undefined) {
|
|
1298
|
+
await pinStore.write({ logId, pin: pinOfLog(updated.log) });
|
|
1299
|
+
}
|
|
1128
1300
|
return { did: updated.did, doc: updated.doc, log: updated.log };
|
|
1129
1301
|
}
|
|
1130
1302
|
/**
|
|
@@ -1140,16 +1312,19 @@ async function enrollClientAnnexTransientClientOnce({ store, ladderSeed, generat
|
|
|
1140
1312
|
* entry is appended ({@link delegatedClientsServiceEntry}). Every other
|
|
1141
1313
|
* service entry, the verification methods, and the relationship arrays are
|
|
1142
1314
|
* preserved untouched. Idempotent: a document already pointing at the DID is
|
|
1143
|
-
* a no-op on the log (
|
|
1315
|
+
* a no-op on the log (and, unless `logOnly`, it republishes `did.json` from
|
|
1316
|
+
* the resolved log, which the enrolled-client caller has the authority to
|
|
1317
|
+
* do).
|
|
1144
1318
|
*
|
|
1145
1319
|
* @param options {object}
|
|
1146
1320
|
* @param options.idStore {WebvhIdStore} the ACCOUNT log's store; with
|
|
1147
1321
|
* `logOnly`, only its log read and `did.jsonl` PUT are used, so the narrow
|
|
1148
1322
|
* delegated seam satisfies it
|
|
1149
|
-
* @param options.
|
|
1150
|
-
* update-key seeds -- or the ladder-rung idiom on a
|
|
1151
|
-
* account (`{ updateSeed: rung0.seed, stagedSeed:
|
|
1152
|
-
* credential-anchored genesis and the
|
|
1323
|
+
* @param options.signer {AccountLogSigner} who signs the pointer entry:
|
|
1324
|
+
* this enrolled client's update-key seeds -- or the ladder-rung idiom on a
|
|
1325
|
+
* ladder-anchored account (`{ updateSeed: rung0.seed, stagedSeed:
|
|
1326
|
+
* rung1.seed }`), as the credential-anchored genesis and the
|
|
1327
|
+
* transient-recovery continuation pass
|
|
1153
1328
|
* @param options.clientAnnexDid {string} the generation to point at
|
|
1154
1329
|
* @param [options.expectedDid] {string} the account DID the log must
|
|
1155
1330
|
* resolve to, from the account pointer
|
|
@@ -1161,24 +1336,56 @@ async function enrollClientAnnexTransientClientOnce({ store, ladderSeed, generat
|
|
|
1161
1336
|
* @param [options.logOnly] {boolean} publish `did.jsonl` only, never the
|
|
1162
1337
|
* `did.json` projection -- the transient-recovery continuation writing
|
|
1163
1338
|
* through the record's bridge delegation, whose narrow scope covers nothing
|
|
1164
|
-
* but the log. The projection
|
|
1165
|
-
*
|
|
1166
|
-
*
|
|
1339
|
+
* but the log. The projection then lags until some caller holding an
|
|
1340
|
+
* `id`-collection writer runs `ensureDidWebProjection` over the resolved
|
|
1341
|
+
* log; on a client-less account that is a transient visit under its
|
|
1342
|
+
* generation delegation. The log stays the source of truth meanwhile, and
|
|
1343
|
+
* the server reads it rather than the projection, so the lag is a `did:web`
|
|
1344
|
+
* verifier's concern alone
|
|
1345
|
+
* @param [options.published] {PublishedWebvhLog} a head the caller already
|
|
1346
|
+
* read and verified under this same pin slot, so the pointer entry builds
|
|
1347
|
+
* on it instead of spending a second round trip on the log the caller just
|
|
1348
|
+
* read or published (the establishment's one-read composition). The FIRST
|
|
1349
|
+
* attempt alone rides it: a lost compare-and-swap means the head is stale
|
|
1350
|
+
* by definition, so the conflict retry re-reads under the pin. That
|
|
1351
|
+
* threaded attempt is EXTRA rather than one of the retry's three: a caller
|
|
1352
|
+
* who saved a read is left with the same conflict budget as one who did not
|
|
1353
|
+
* @returns {Promise<{ did: string, doc: DIDDoc,
|
|
1354
|
+
* published: PublishedWebvhLog }>} `published` is the head this call
|
|
1355
|
+
* leaves standing: the post-entry one, paired with its publish's own ETag,
|
|
1356
|
+
* when the entry was appended; the head it stood on verbatim when the
|
|
1357
|
+
* document already pointed at the DID
|
|
1167
1358
|
*/
|
|
1168
|
-
export async function setDelegatedClientsPointer(
|
|
1169
|
-
|
|
1359
|
+
export async function setDelegatedClientsPointer({ published: threadedHead, ...rest }) {
|
|
1360
|
+
if (threadedHead !== undefined) {
|
|
1361
|
+
try {
|
|
1362
|
+
return await setDelegatedClientsPointerOnce({
|
|
1363
|
+
...rest,
|
|
1364
|
+
published: threadedHead
|
|
1365
|
+
});
|
|
1366
|
+
}
|
|
1367
|
+
catch (err) {
|
|
1368
|
+
// A lost compare-and-swap on the threaded head says only that the head
|
|
1369
|
+
// is stale; the retry below re-reads under the pin with its whole
|
|
1370
|
+
// budget. Every other failure is the caller's.
|
|
1371
|
+
if (!(err instanceof WebvhLogConflictError)) {
|
|
1372
|
+
throw err;
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
return withLogConflictRetry(() => setDelegatedClientsPointerOnce(rest));
|
|
1170
1377
|
}
|
|
1171
1378
|
/**
|
|
1172
1379
|
* ONE attempt of {@link setDelegatedClientsPointer}, re-invoked by that
|
|
1173
1380
|
* function's conflict retry and exported for a caller that runs its own.
|
|
1174
1381
|
* A caller whose retry loop also attributes the signing rung must use this
|
|
1175
1382
|
* form: the wrapper's inner retry would re-invoke the attempt with the SAME
|
|
1176
|
-
*
|
|
1383
|
+
* signer, and a rung a racing ceremony consumed meanwhile can never
|
|
1177
1384
|
* become authorized by re-reading, so the attempt would fail on the plain
|
|
1178
1385
|
* not-authorized refusal instead of surfacing the conflict the caller's loop
|
|
1179
1386
|
* knows how to re-attribute from.
|
|
1180
1387
|
*
|
|
1181
|
-
* A caller that already read the head its
|
|
1388
|
+
* A caller that already read the head its signer was attributed
|
|
1182
1389
|
* against passes it as `published`, and the entry is built on exactly that
|
|
1183
1390
|
* head. A racing entry landing in between then loses the CAS on the PUT and
|
|
1184
1391
|
* surfaces as a {@link WebvhLogConflictError}, which is what a re-attributing
|
|
@@ -1189,72 +1396,64 @@ export async function setDelegatedClientsPointer(options) {
|
|
|
1189
1396
|
* @param [options.published] {PublishedWebvhLog} the verified head to build
|
|
1190
1397
|
* this entry on, when the caller has already read it under the same pin;
|
|
1191
1398
|
* absent, the attempt reads the head itself
|
|
1192
|
-
* @returns {Promise<{ did: string, doc: DIDDoc
|
|
1399
|
+
* @returns {Promise<{ did: string, doc: DIDDoc,
|
|
1400
|
+
* published: PublishedWebvhLog }>}
|
|
1193
1401
|
*/
|
|
1194
|
-
export async function setDelegatedClientsPointerOnce({ idStore,
|
|
1402
|
+
export async function setDelegatedClientsPointerOnce({ idStore, signer, clientAnnexDid, expectedDid, pinStore, logId, logOnly = false, published: alreadyRead }) {
|
|
1195
1403
|
// Refuses a malformed target before anything is read or written.
|
|
1196
1404
|
clientAnnexDidParts({ did: clientAnnexDid });
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
...(expectedDid !== undefined ? { expectedDid } : {}),
|
|
1201
|
-
...(pinStore !== undefined ? { pinStore } : {}),
|
|
1202
|
-
...(logId !== undefined ? { logId } : {})
|
|
1203
|
-
}));
|
|
1204
|
-
if (!published) {
|
|
1205
|
-
throw new Error('did:webvh: did.jsonl is missing; nothing to point at a client annex.');
|
|
1206
|
-
}
|
|
1207
|
-
const { did, doc } = published;
|
|
1208
|
-
if (delegatedClientsPointer({ doc }) === clientAnnexDid) {
|
|
1209
|
-
if (!logOnly) {
|
|
1210
|
-
await concludeWithPublishedLog({ idStore, published });
|
|
1211
|
-
}
|
|
1212
|
-
return { did, doc };
|
|
1213
|
-
}
|
|
1214
|
-
// The entry is signed by this client's active update key; a log that does
|
|
1215
|
-
// not authorize it (a rotation torn elsewhere) must heal first.
|
|
1216
|
-
const activeKey = await updateKeyMultibase({ seed: updateKeys.updateSeed });
|
|
1217
|
-
if (!published.updateKeys.includes(activeKey)) {
|
|
1218
|
-
throw new Error("did:webvh: the published log does not authorize this client's active " +
|
|
1219
|
-
'update key; finalize the pending rotation before re-pointing the ' +
|
|
1220
|
-
'delegated-clients entry.');
|
|
1221
|
-
}
|
|
1222
|
-
await assertCarryOverCommitments({ published });
|
|
1223
|
-
const services = servicesPointedAtClientAnnex({
|
|
1224
|
-
doc,
|
|
1225
|
-
accountDid: did,
|
|
1226
|
-
clientAnnexDid
|
|
1227
|
-
});
|
|
1228
|
-
const signer = await updateKeySigner({ seed: updateKeys.updateSeed });
|
|
1229
|
-
const updated = await updateDID({
|
|
1230
|
-
log: published.log,
|
|
1405
|
+
let settled;
|
|
1406
|
+
const outcome = await signAccountEntry({
|
|
1407
|
+
idStore,
|
|
1231
1408
|
signer,
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1409
|
+
...(alreadyRead !== undefined ? { published: alreadyRead } : {}),
|
|
1410
|
+
...(expectedDid !== undefined ? { expectedDid } : {}),
|
|
1411
|
+
...(pinStore ? { pinStore } : {}),
|
|
1412
|
+
...(logId !== undefined ? { logId } : {}),
|
|
1413
|
+
missingMessage: 'did:webvh: did.jsonl is missing; nothing to point at a client annex.',
|
|
1414
|
+
verb: 're-pointing the delegated-clients entry',
|
|
1415
|
+
logOnly,
|
|
1416
|
+
build: async ({ published }) => {
|
|
1417
|
+
const { did, doc } = published;
|
|
1418
|
+
if (delegatedClientsPointer({ doc }) === clientAnnexDid) {
|
|
1419
|
+
if (!logOnly && signer.kind === 'client') {
|
|
1420
|
+
await concludeWithPublishedLog({ idStore, published });
|
|
1421
|
+
}
|
|
1422
|
+
// The head verbatim: the projection PUT touches no log, so this
|
|
1423
|
+
// read's own ETag is still the log's validator.
|
|
1424
|
+
settled = { did, doc, published };
|
|
1425
|
+
return undefined;
|
|
1426
|
+
}
|
|
1427
|
+
return {
|
|
1428
|
+
services: servicesPointedAtClientAnnex({
|
|
1429
|
+
doc,
|
|
1430
|
+
accountDid: did,
|
|
1431
|
+
clientAnnexDid
|
|
1432
|
+
})
|
|
1433
|
+
};
|
|
1434
|
+
}
|
|
1239
1435
|
});
|
|
1240
|
-
if (
|
|
1241
|
-
|
|
1242
|
-
store: idStore,
|
|
1243
|
-
log: updated.log,
|
|
1244
|
-
ifMatch: published.etag
|
|
1245
|
-
});
|
|
1246
|
-
}
|
|
1247
|
-
else {
|
|
1248
|
-
await publishUpdatedLog({ idStore, updated, ifMatch: published.etag });
|
|
1436
|
+
if (settled) {
|
|
1437
|
+
return settled;
|
|
1249
1438
|
}
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1439
|
+
const updated = outcome.updated;
|
|
1440
|
+
return {
|
|
1441
|
+
did: updated.did,
|
|
1442
|
+
doc: updated.doc,
|
|
1443
|
+
// The post-entry head, from what `updateDID` already resolved plus this
|
|
1444
|
+
// publish's own validator: the update-key parameters are the ones the
|
|
1445
|
+
// entry re-stated unchanged above, so nothing is re-resolved or re-read.
|
|
1446
|
+
published: {
|
|
1447
|
+
log: updated.log,
|
|
1448
|
+
did: updated.did,
|
|
1449
|
+
// Detached from the entry's own `state`, as every other producer of
|
|
1450
|
+
// this type is, so a consumer editing the document cannot edit the log.
|
|
1451
|
+
doc: structuredClone(updated.doc),
|
|
1452
|
+
updateKeys: updated.meta.updateKeys,
|
|
1453
|
+
nextKeyHashes: updated.meta.nextKeyHashes,
|
|
1454
|
+
...(outcome.etag !== undefined ? { etag: outcome.etag } : {})
|
|
1455
|
+
}
|
|
1456
|
+
};
|
|
1258
1457
|
}
|
|
1259
1458
|
/**
|
|
1260
1459
|
* The whole transient-enrollment ceremony as the enrollee runs it: resolve
|
|
@@ -1291,9 +1490,15 @@ export async function setDelegatedClientsPointerOnce({ idStore, updateKeys, clie
|
|
|
1291
1490
|
* @param [options.maxRounds] {number} how many pointer moves to chase
|
|
1292
1491
|
* before giving up (a GC pass is quarterly, so more than one mid-ceremony
|
|
1293
1492
|
* move means something else is wrong)
|
|
1493
|
+
* @param [options.published] {PublishedWebvhLog} a generation head the
|
|
1494
|
+
* caller already read and verified under this same pin store (the readiness
|
|
1495
|
+
* stage's, when it published nothing to that log). Round 0 alone rides it,
|
|
1496
|
+
* and only when it is the generation the account document points at NOW: a
|
|
1497
|
+
* head for any other generation is ignored and the round reads fresh, as
|
|
1498
|
+
* every later round does
|
|
1294
1499
|
* @returns {Promise<{ clientAnnexDid: string, doc: DIDDoc, log: DIDLog }>}
|
|
1295
1500
|
*/
|
|
1296
|
-
export async function enrollTransientClient({ readAccountDocument, storeForGenerationId, ladderSeed, transientKeyMultibase, mintGenerationDelegation: mintDelegation, pinStore, maxRounds = 3 }) {
|
|
1501
|
+
export async function enrollTransientClient({ readAccountDocument, storeForGenerationId, ladderSeed, transientKeyMultibase, mintGenerationDelegation: mintDelegation, pinStore, maxRounds = 3, published: threadedHead }) {
|
|
1297
1502
|
let accountDoc = await readAccountDocument();
|
|
1298
1503
|
for (let round = 0; round < maxRounds; round++) {
|
|
1299
1504
|
const clientAnnexDid = delegatedClientsPointer({ doc: accountDoc });
|
|
@@ -1304,12 +1509,19 @@ export async function enrollTransientClient({ readAccountDocument, storeForGener
|
|
|
1304
1509
|
const { spaceId, generationId } = clientAnnexDidParts({
|
|
1305
1510
|
did: clientAnnexDid
|
|
1306
1511
|
});
|
|
1512
|
+
// Round 0's threaded head, and only for the generation the pointer names:
|
|
1513
|
+
// a head read before a GC swap belongs to the abandoned generation and
|
|
1514
|
+
// says nothing about the one this round enrolls into.
|
|
1515
|
+
const head = round === 0 && threadedHead?.did === clientAnnexDid
|
|
1516
|
+
? threadedHead
|
|
1517
|
+
: undefined;
|
|
1307
1518
|
const enrolled = await enrollClientAnnexTransientClient({
|
|
1308
1519
|
store: storeForGenerationId(generationId),
|
|
1309
1520
|
ladderSeed,
|
|
1310
1521
|
generationId,
|
|
1311
1522
|
transientKeyMultibase,
|
|
1312
1523
|
expectedDid: clientAnnexDid,
|
|
1524
|
+
...(head !== undefined ? { published: head } : {}),
|
|
1313
1525
|
...(mintDelegation !== undefined
|
|
1314
1526
|
? { mintGenerationDelegation: mintDelegation }
|
|
1315
1527
|
: {}),
|
|
@@ -1328,12 +1540,98 @@ export async function enrollTransientClient({ readAccountDocument, storeForGener
|
|
|
1328
1540
|
throw new Error('client annex: the delegated-clients pointer kept moving across ' +
|
|
1329
1541
|
`${String(maxRounds)} enrollment rounds; giving up.`);
|
|
1330
1542
|
}
|
|
1543
|
+
/**
|
|
1544
|
+
* The fresh-generation block every separate-pointer-entry caller runs: mint
|
|
1545
|
+
* a generation in the annex Space, install its generation delegation, then
|
|
1546
|
+
* append the account document's pointer entry naming it. The pointer write is
|
|
1547
|
+
* the injected step (`point`): the establishment signs it as a client under
|
|
1548
|
+
* a rung the caller attributed, the transient visit moves it as the ladder
|
|
1549
|
+
* with its reveal-and-retry form. A caller with a step that must land
|
|
1550
|
+
* between the install and the pointer entry (the establishment's bootstrap
|
|
1551
|
+
* arm, whose Space still answers to its creation controller until it flips
|
|
1552
|
+
* it to the account DID) supplies `beforePointerEntry`.
|
|
1553
|
+
*
|
|
1554
|
+
* The install stands on the head the mint just published rather than
|
|
1555
|
+
* re-reading the log this run wrote a moment ago -- but only when that head
|
|
1556
|
+
* carries the PUT's own ETag, since the install's entry publishes under a
|
|
1557
|
+
* compare-and-swap and a head with no validator would degrade that to an
|
|
1558
|
+
* unconditional write. With no ETag the install reads for itself. Either way
|
|
1559
|
+
* its own publish advances the pin, so this generation's pin slot is
|
|
1560
|
+
* established by this run.
|
|
1561
|
+
*
|
|
1562
|
+
* @param options {object}
|
|
1563
|
+
* @param options.was {WasClient} the storage client the mint and the
|
|
1564
|
+
* install write through
|
|
1565
|
+
* @param options.wasServerUrl {string}
|
|
1566
|
+
* @param options.spaceId {string} the auxiliary annex Space's id
|
|
1567
|
+
* @param options.controller {string} the Space's creation controller (see
|
|
1568
|
+
* {@link mintCredentialClientAnnexGeneration})
|
|
1569
|
+
* @param options.ladderSeed {Uint8Array} the minting credential's ladder
|
|
1570
|
+
* seed
|
|
1571
|
+
* @param options.mintGenerationDelegation {Function}
|
|
1572
|
+
* `({ clientAnnexDid }) => Promise<IZcap>`
|
|
1573
|
+
* @param options.point {Function} `(clientAnnexDid) => Promise<T>` -- the
|
|
1574
|
+
* pointer entry, whose result rides the return verbatim
|
|
1575
|
+
* @param [options.capability] {IZcap} the sibling delegation the annex
|
|
1576
|
+
* writes ride, for a caller holding a standing invocation authority
|
|
1577
|
+
* @param [options.beforePointerEntry] {Function}
|
|
1578
|
+
* `({ minted }) => Promise<void>` -- runs after the install and before the
|
|
1579
|
+
* pointer entry, with the mint's published head (its `spaceDescription`
|
|
1580
|
+
* included)
|
|
1581
|
+
* @param [options.pinStore] {ResourceLogPinStore} chain-head pins; the
|
|
1582
|
+
* generation's slot is derived here
|
|
1583
|
+
* @param [options.now] {number} epoch milliseconds, for tests
|
|
1584
|
+
* @returns {Promise<{ clientAnnexDid: string, generationDelegation: IZcap,
|
|
1585
|
+
* pointed: T }>}
|
|
1586
|
+
*/
|
|
1587
|
+
export async function mintPointedClientAnnexGeneration({ was, wasServerUrl, spaceId, controller, ladderSeed, mintGenerationDelegation, point, capability, beforePointerEntry, pinStore, now }) {
|
|
1588
|
+
const minted = await mintCredentialClientAnnexGeneration({
|
|
1589
|
+
was,
|
|
1590
|
+
wasServerUrl,
|
|
1591
|
+
spaceId,
|
|
1592
|
+
controller,
|
|
1593
|
+
ladderSeed,
|
|
1594
|
+
...(capability !== undefined ? { capability } : {})
|
|
1595
|
+
});
|
|
1596
|
+
const ensured = await ensureGenerationDelegationCurrent({
|
|
1597
|
+
store: clientAnnexLogStore({
|
|
1598
|
+
was,
|
|
1599
|
+
spaceId,
|
|
1600
|
+
generationId: minted.generationId,
|
|
1601
|
+
...(capability !== undefined ? { capability } : {})
|
|
1602
|
+
}),
|
|
1603
|
+
ladderSeed,
|
|
1604
|
+
generationId: minted.generationId,
|
|
1605
|
+
mintGenerationDelegation,
|
|
1606
|
+
expectedDid: minted.did,
|
|
1607
|
+
...(minted.etag !== undefined ? { published: minted } : {}),
|
|
1608
|
+
...(pinStore !== undefined
|
|
1609
|
+
? {
|
|
1610
|
+
pinStore,
|
|
1611
|
+
logId: clientAnnexLogPinId({
|
|
1612
|
+
spaceId,
|
|
1613
|
+
generationId: minted.generationId
|
|
1614
|
+
})
|
|
1615
|
+
}
|
|
1616
|
+
: {}),
|
|
1617
|
+
...(now !== undefined ? { now } : {})
|
|
1618
|
+
});
|
|
1619
|
+
if (beforePointerEntry !== undefined) {
|
|
1620
|
+
await beforePointerEntry({ minted });
|
|
1621
|
+
}
|
|
1622
|
+
const pointed = await point(minted.did);
|
|
1623
|
+
return {
|
|
1624
|
+
clientAnnexDid: minted.did,
|
|
1625
|
+
generationDelegation: ensured.delegation,
|
|
1626
|
+
pointed
|
|
1627
|
+
};
|
|
1628
|
+
}
|
|
1331
1629
|
/**
|
|
1332
1630
|
* RENEW PRECEDES MINT: the blocking pre-mint stage a transient App Connect
|
|
1333
1631
|
* approval runs before delegating any grant. Reads the annex document
|
|
1334
1632
|
* and hands back its embedded generation delegation -- renewing it first
|
|
1335
|
-
* when it is
|
|
1336
|
-
*
|
|
1633
|
+
* when it is stale under the house policy ({@link standingZcapStale}): a
|
|
1634
|
+
* fresh delegation is minted through the caller's closure
|
|
1337
1635
|
* (ladder-signed -- the renewal must not depend on the very delegation it
|
|
1338
1636
|
* replaces; published through the store, which in a transient session is
|
|
1339
1637
|
* the credential's sibling delegation, so even a hard-expired delegation is
|
|
@@ -1344,14 +1642,16 @@ export async function enrollTransientClient({ readAccountDocument, storeForGener
|
|
|
1344
1642
|
* same way (the GC ceremony's own install stage and the first-VM install
|
|
1345
1643
|
* make this rare; a heal, not a policy).
|
|
1346
1644
|
*
|
|
1347
|
-
*
|
|
1348
|
-
*
|
|
1349
|
-
*
|
|
1350
|
-
*
|
|
1351
|
-
*
|
|
1352
|
-
*
|
|
1353
|
-
*
|
|
1354
|
-
*
|
|
1645
|
+
* That policy is three axes. Expiry -- past, or inside the 30-day renewal
|
|
1646
|
+
* window. SIGNER DEATH, which an `accountDoc` adds: a standing delegation
|
|
1647
|
+
* whose proof key is no longer in the supplied verified account document has
|
|
1648
|
+
* rotted under the current-key-set rule (the enrolled client that minted it
|
|
1649
|
+
* was revoked, or the ladder VM that signed it left with the first
|
|
1650
|
+
* self-enrollment). And RETIREMENT, the projected post-edit reading a caller
|
|
1651
|
+
* asks for with `retiringKeyMultibases`, for a key still listed whose
|
|
1652
|
+
* authority the ceremony is about to end. No revocation POST accompanies the
|
|
1653
|
+
* replacement: a rotted chain no longer verifies at the revocation endpoint,
|
|
1654
|
+
* and the expiry-renewal path never revoked either.
|
|
1355
1655
|
*
|
|
1356
1656
|
* Failure is the caller's failure: a renewal that cannot complete throws,
|
|
1357
1657
|
* and the App Connect approval fails with the standard retryable-ceremony
|
|
@@ -1380,64 +1680,96 @@ export async function enrollTransientClient({ readAccountDocument, storeForGener
|
|
|
1380
1680
|
* @param [options.accountDoc] {PublishedKeyDocument} the locally VERIFIED
|
|
1381
1681
|
* account document; supplied, a standing delegation whose proof key it no
|
|
1382
1682
|
* longer lists is replaced (the signer-death axis above)
|
|
1383
|
-
* @param [options.
|
|
1384
|
-
*
|
|
1385
|
-
*
|
|
1386
|
-
*
|
|
1683
|
+
* @param [options.retiringKeyMultibases] {string[]} keys whose authority is
|
|
1684
|
+
* about to end, read as a projected post-edit document: a standing
|
|
1685
|
+
* delegation one of them signed is replaced even though the served document
|
|
1686
|
+
* still lists the key. The last-client transition's replacement stage names
|
|
1687
|
+
* its own ladder VM (whose delegations it revokes server-side in the next
|
|
1688
|
+
* breath, a state no client-side predicate can read) and the client its
|
|
1689
|
+
* removal entry has yet to strike
|
|
1387
1690
|
* @param [options.now] {number} epoch milliseconds, for tests
|
|
1388
|
-
* @
|
|
1691
|
+
* @param [options.published] {PublishedWebvhLog} a head the caller already
|
|
1692
|
+
* read and verified under this same pin slot, so the stage builds on it
|
|
1693
|
+
* instead of spending a second round trip on the same log (the transient
|
|
1694
|
+
* visit's one-read composition). The FIRST attempt alone rides it: a lost
|
|
1695
|
+
* compare-and-swap means the head is stale by definition, so the conflict
|
|
1696
|
+
* retry re-reads under the pin. That threaded attempt is EXTRA rather than
|
|
1697
|
+
* one of the retry's three: a caller who saved a read is left with the same
|
|
1698
|
+
* conflict budget as one who did not
|
|
1699
|
+
* @returns {Promise<{ delegation: IZcap, renewed: boolean,
|
|
1700
|
+
* published?: PublishedWebvhLog }>} `published` is the verified head this
|
|
1701
|
+
* pass stood on -- the one read, or the one handed in -- and is present
|
|
1702
|
+
* ONLY when `renewed` is false. The two store implementations differ on
|
|
1703
|
+
* what a renewal's publish hands back: the controller-tier store forwards
|
|
1704
|
+
* the PUT's own ETag, while the delegated store discards the response and
|
|
1705
|
+
* yields none. This stage takes either, and cannot tell which from here, so
|
|
1706
|
+
* it stays conservative and passes no post-renewal head on; a caller
|
|
1707
|
+
* wanting one reads for itself
|
|
1389
1708
|
*/
|
|
1390
|
-
export async function ensureGenerationDelegationCurrent(
|
|
1391
|
-
|
|
1709
|
+
export async function ensureGenerationDelegationCurrent({ published: threadedHead, ...rest }) {
|
|
1710
|
+
if (threadedHead !== undefined) {
|
|
1711
|
+
try {
|
|
1712
|
+
return await ensureGenerationDelegationCurrentOnce({
|
|
1713
|
+
...rest,
|
|
1714
|
+
published: threadedHead
|
|
1715
|
+
});
|
|
1716
|
+
}
|
|
1717
|
+
catch (err) {
|
|
1718
|
+
// A lost compare-and-swap on the threaded head says only that the head
|
|
1719
|
+
// is stale; the retry below re-reads under the pin with its whole
|
|
1720
|
+
// budget. Every other failure is the caller's.
|
|
1721
|
+
if (!(err instanceof WebvhLogConflictError)) {
|
|
1722
|
+
throw err;
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
return withLogConflictRetry(() => ensureGenerationDelegationCurrentOnce(rest));
|
|
1392
1727
|
}
|
|
1393
1728
|
/**
|
|
1394
1729
|
* One attempt of {@link ensureGenerationDelegationCurrent}, re-invoked by the
|
|
1395
1730
|
* conflict retry (with the same signing key -- static rung 0 has no
|
|
1396
|
-
* advanced-rung retry shape).
|
|
1731
|
+
* advanced-rung retry shape) and with no threaded head after the first.
|
|
1397
1732
|
*
|
|
1398
1733
|
* @param options {object} see {@link ensureGenerationDelegationCurrent}
|
|
1399
|
-
* @returns {Promise<{ delegation: IZcap, renewed: boolean
|
|
1734
|
+
* @returns {Promise<{ delegation: IZcap, renewed: boolean,
|
|
1735
|
+
* published?: PublishedWebvhLog }>}
|
|
1400
1736
|
*/
|
|
1401
|
-
async function ensureGenerationDelegationCurrentOnce({ store, ladderSeed, generationId, mintGenerationDelegation: mintDelegation, expectedDid, pinStore, logId, accountDoc,
|
|
1737
|
+
async function ensureGenerationDelegationCurrentOnce({ store, ladderSeed, generationId, mintGenerationDelegation: mintDelegation, expectedDid, pinStore, logId, accountDoc, retiringKeyMultibases = [], now, published: alreadyRead }) {
|
|
1402
1738
|
assertGenerationId(generationId);
|
|
1403
|
-
const published =
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1739
|
+
const published = alreadyRead !== undefined
|
|
1740
|
+
? assertPublishedLogDid({
|
|
1741
|
+
published: alreadyRead,
|
|
1742
|
+
...(expectedDid !== undefined ? { expectedDid } : {})
|
|
1743
|
+
})
|
|
1744
|
+
: await readClientAnnexLogOrThrow({
|
|
1745
|
+
store,
|
|
1746
|
+
...(expectedDid !== undefined ? { expectedDid } : {}),
|
|
1747
|
+
...(pinStore !== undefined ? { pinStore } : {}),
|
|
1748
|
+
...(logId !== undefined ? { logId } : {})
|
|
1749
|
+
});
|
|
1409
1750
|
const { did, doc } = published;
|
|
1410
1751
|
const standing = embeddedGenerationDelegation({ doc });
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
doc: accountDoc,
|
|
1415
|
-
|
|
1416
|
-
? { delegationKeyId: delegationProofKeyId(standing) }
|
|
1417
|
-
: {})
|
|
1418
|
-
});
|
|
1419
|
-
if (!force &&
|
|
1420
|
-
standing !== undefined &&
|
|
1421
|
-
!signerRotted &&
|
|
1422
|
-
!zcapExpiring({
|
|
1423
|
-
...(standing.expires !== undefined
|
|
1424
|
-
? { expires: standing.expires }
|
|
1425
|
-
: {}),
|
|
1752
|
+
if (standing !== undefined &&
|
|
1753
|
+
!standingZcapStale({
|
|
1754
|
+
zcap: standing,
|
|
1755
|
+
...(accountDoc !== undefined ? { doc: accountDoc } : {}),
|
|
1756
|
+
retiringKeyMultibases,
|
|
1426
1757
|
...(now !== undefined ? { now } : {})
|
|
1427
1758
|
})) {
|
|
1428
|
-
return { delegation: standing, renewed: false };
|
|
1759
|
+
return { delegation: standing, renewed: false, published };
|
|
1429
1760
|
}
|
|
1430
1761
|
// The rung refusal precedes the mint: nothing is delegated for a writer
|
|
1431
1762
|
// who cannot publish the entry that would carry it.
|
|
1432
|
-
const rung = await
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1763
|
+
const rung = await admitActingRung({
|
|
1764
|
+
ladderSeed,
|
|
1765
|
+
generationId,
|
|
1766
|
+
updateKeys: published.updateKeys,
|
|
1767
|
+
nextKeyHashes: published.nextKeyHashes,
|
|
1768
|
+
message: "client annex: the log commits neither this credential's rung-0 key nor " +
|
|
1437
1769
|
'its hash; a credential bound mid-generation cannot renew the ' +
|
|
1438
1770
|
'generation delegation until a writer commits its hash or the next ' +
|
|
1439
|
-
'GC swap does.'
|
|
1440
|
-
}
|
|
1771
|
+
'GC swap does.'
|
|
1772
|
+
});
|
|
1441
1773
|
await assertCarryOverCommitments({ published });
|
|
1442
1774
|
const fresh = await mintDelegation({ clientAnnexDid: did });
|
|
1443
1775
|
const signer = await updateKeySigner({ seed: rung.seed });
|
|
@@ -1457,6 +1789,13 @@ async function ensureGenerationDelegationCurrentOnce({ store, ladderSeed, genera
|
|
|
1457
1789
|
})
|
|
1458
1790
|
});
|
|
1459
1791
|
await putLogResource({ store, log: updated.log, ifMatch: published.etag });
|
|
1792
|
+
// Advance the pin to what this entry just published, so a host serving the
|
|
1793
|
+
// pre-entry log straight afterwards is refused as a rollback on the next
|
|
1794
|
+
// read (equal-to-pin would otherwise be accepted, and a later stage built on
|
|
1795
|
+
// the stale head would miss this entry).
|
|
1796
|
+
if (pinStore && logId !== undefined) {
|
|
1797
|
+
await pinStore.write({ logId, pin: pinOfLog(updated.log) });
|
|
1798
|
+
}
|
|
1460
1799
|
return { delegation: fresh, renewed: true };
|
|
1461
1800
|
}
|
|
1462
1801
|
/**
|
|
@@ -1527,17 +1866,15 @@ async function retireClientAnnexRungOnce({ store, retiredLadderSeed, actingLadde
|
|
|
1527
1866
|
}
|
|
1528
1867
|
// The acting rung must be committed AFTER the retired members are
|
|
1529
1868
|
// excluded, so the retired credential can never sign its own strike.
|
|
1530
|
-
const acting = await
|
|
1869
|
+
const acting = await admitActingRung({
|
|
1531
1870
|
ladderSeed: actingLadderSeed,
|
|
1532
|
-
generationId
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
if (!revealed && !remainingHashes.includes(actingHash)) {
|
|
1537
|
-
throw new ClientAnnexRungUncommittedError("client annex: the log commits neither the acting credential's rung-0 " +
|
|
1871
|
+
generationId,
|
|
1872
|
+
updateKeys: remainingKeys,
|
|
1873
|
+
nextKeyHashes: remainingHashes,
|
|
1874
|
+
message: "client annex: the log commits neither the acting credential's rung-0 " +
|
|
1538
1875
|
'key nor its hash (or it is the retired rung itself); the strike ' +
|
|
1539
|
-
'needs a distinct committed writer -- swap the generation instead.'
|
|
1540
|
-
}
|
|
1876
|
+
'needs a distinct committed writer -- swap the generation instead.'
|
|
1877
|
+
});
|
|
1541
1878
|
await assertCarryOverCommitments({ published });
|
|
1542
1879
|
const signer = await updateKeySigner({ seed: acting.seed });
|
|
1543
1880
|
const updated = await updateDID({
|
|
@@ -1617,17 +1954,15 @@ async function commitClientAnnexRungOnce({ store, boundLadderSeed, actingLadderS
|
|
|
1617
1954
|
// generation the bound credential itself minted.
|
|
1618
1955
|
return { committed: false };
|
|
1619
1956
|
}
|
|
1620
|
-
const acting = await
|
|
1957
|
+
const acting = await admitActingRung({
|
|
1621
1958
|
ladderSeed: actingLadderSeed,
|
|
1622
|
-
generationId
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
if (!revealed && !published.nextKeyHashes.includes(actingHash)) {
|
|
1627
|
-
throw new ClientAnnexRungUncommittedError("client annex: the log commits neither the acting credential's rung-0 " +
|
|
1959
|
+
generationId,
|
|
1960
|
+
updateKeys: published.updateKeys,
|
|
1961
|
+
nextKeyHashes: published.nextKeyHashes,
|
|
1962
|
+
message: "client annex: the log commits neither the acting credential's rung-0 " +
|
|
1628
1963
|
'key nor its hash; a commit entry needs a committed writer -- the ' +
|
|
1629
|
-
'bound credential stays locked out until the next GC swap.'
|
|
1630
|
-
}
|
|
1964
|
+
'bound credential stays locked out until the next GC swap.'
|
|
1965
|
+
});
|
|
1631
1966
|
await assertCarryOverCommitments({ published });
|
|
1632
1967
|
const signer = await updateKeySigner({ seed: acting.seed });
|
|
1633
1968
|
const updated = await updateDID({
|