import type { DIDDoc, DIDLog, VerificationMethod } from '@interop/did-method-webvh'; import type { AccountLogSigner } from './accountEntry.js'; import type { PublishedWebvhLog, WebvhIdStore } from './didWebvh.js'; /** * The public halves of the client being revoked, as the document and log * carry them: its signing-key multibase and its active update key. The * staged-key hash is deliberately absent -- it is recovered from the log (see * the module doc), since no other party ever held the staged key. * * The key-agreement half is deliberately absent too: the removal reads every * key-agreement method the client's controller marker claims off the document * itself, so no caller-supplied key-agreement key is ever needed to revoke the * client completely. */ export interface RevokedClientKeys { signingKeyMultibase: string; updateKeyMultibase: string; } /** * Thrown when the log attribution cannot isolate the revoked client's staged * commitment to a single hash -- more than one candidate survives after the * known latent (recovery-code) hashes are excluded, and the decision-0007 * append-order rule cannot resolve the residue positionally either (the * client's update-key hash is not among the entry's additions, or the * addition after it is not a surviving candidate). Refusing beats guessing: * see the module doc. * * **`name` is a stable contract.** It is always the string * `'StagedCommitmentAmbiguousError'`, and a consumer should match on that * rather than on `instanceof`: a wallet app that links this package (or holds * two copies of it through a dependency tree) gets a different class object for * the same error, so `instanceof` silently fails there while the name does not. */ export declare class StagedCommitmentAmbiguousError extends Error { candidates: string[]; constructor({ candidates }: { candidates: string[]; }); } /** * What a removal edit strikes, computed from the published log before any * entry is built: the removed client's verification-method ids, whether any * of them still stand (`vmPresent`), the update key the LOG states for the * client now ({@link clientRemovalTarget} re-derives a stale or staged key by * attribution), that key's carry-over hash, and whether anything at all is * left to remove (`present`). Shared by the revocation entry * ({@link revokeWebvhClient}) and the ladder-signed forget entry * (`forgetWebvhClient` in the unlock module), so the two removal shapes can * never drift. */ export interface ClientRemovalTarget { signingVmId: string; keyAgreementVmIds: Set; vmPresent: boolean; removedUpdateKey: string; removedHash: string; keyPresent: boolean; hashPresent: boolean; present: boolean; } /** * Computes what removing one enrolled client strikes from the published * document and log (see {@link ClientRemovalTarget}). Pure read: nothing is * published, and an absent client resolves with `present: false` rather than * throwing (the idempotent no-op the callers fall through to). * * @param options {object} * @param options.published {PublishedWebvhLog} * @param options.client {RevokedClientKeys} the removed client's public * halves; an `updateKeyMultibase` the log does not authorize (stale, or the * client's staged key) is re-derived from the log * @returns {Promise} */ export declare function clientRemovalTarget({ published, client }: { published: PublishedWebvhLog; client: RevokedClientKeys; }): Promise; /** * Builds the removal entry's document and parameter fields from a computed * target: the client's verification methods out of the document and all five * relationship arrays, its update key out of `updateKeys`, and its carry-over * and staged hashes out of `nextKeyHashes` (the staged hash recovered by log * attribution -- see the module doc for why leaving it would be a re-seizure * credential, and where the attribution is ambiguous). The caller supplies * these to `updateDID` beside its own signer -- an enrolled client's update * key for a revocation, a revealed ladder rung for a forget. * * The latent commitments excluded from that attribution are derived here * from the log (`standingCredentialLatentHashes`), so no caller has to know * the account's standing credentials to remove a client correctly; a caller's * own list is excluded beside them as a cross-check and vouches for what the * walk could not read (a credential an earlier recovery's add-and-retire * entry introduced has no anchor, and a registry still names its hash). The * derived set never overrides the decision-0007 position, so a walk that * over-claimed cannot leave the client's own staged hash standing. * * @param options {object} * @param options.published {PublishedWebvhLog} * @param options.target {ClientRemovalTarget} * @param [options.knownLatentHashes] {string[]} standing latent commitments * the caller vouches for beyond the derived set (a recovery registry's * update-key hashes, a ladder's own rung hashes), excluded from the * staged-hash attribution alongside it * @returns {Promise} the `updateDID` field bundle */ export declare function clientRemovalFields({ published, target, knownLatentHashes }: { published: PublishedWebvhLog; target: ClientRemovalTarget; knownLatentHashes?: string[]; }): Promise<{ updateKeys: string[]; nextKeyHashes: string[]; verificationMethods: VerificationMethod[]; authentication: string[]; assertionMethod: string[]; keyAgreement: string[]; capabilityInvocation: string[]; capabilityDelegation: string[]; }>; /** * REVOCATION (run by another enrolled client, root authority): removes an * enrolled wallet client from the published document -- its two verification * methods out of the document and all five relationship arrays, its update * key out of `updateKeys`, and its carry-over and staged hashes out of * `nextKeyHashes` -- in one log entry (a removal reveals no key, so * prerotation forces no commit entry). Under the current-key-set rule this * single edit is also the revoked client's pull axis everywhere: its * invocations and every delegation it signed stop verifying the moment its * verification method leaves the document. * * Idempotent: a client with no remaining presence (verification methods, * update key, commitments all gone) is a no-op on the log (it still republishes * `did.json` from the resolved log, healing a torn earlier publish of this * cascade -- the revoking client invokes as the controller, so it may write * the projection), so a naive re-run after a mid-cascade crash converges * without forking the log. * * The supplied `updateKeyMultibase` is treated as a snapshot, not as truth: a * client that self-rotated between the caller's listing and this call -- or a * caller that supplied the client's staged key rather than its active one -- * is revoked at the key the LOG states, re-derived by attribution (see * {@link currentRevokedUpdateKey}). Without that, a key that is not in * `updateKeys` would strike nothing out of it and the call would report * success over a client that kept full log-update authority. * * Self-revocation is refused on the CLIENT arm: the entry is signed by that * client's active update key, and a client that removed its own key could not * have signed the removal the resolver will verify (and would strand the * cascade that follows the document edit). Revoking the last remaining client * is refused by the same guard. * * The LADDER arm has no self. A standing credential's rung signs the removal * through the credential's bridge delegation, so any enrolled client may be * removed, the last one included: the account then stands ladder-anchored, * the shape a credential-anchored signup produces (`decisions/0017`). The * entry strikes the attributed staged hash exactly as the client arm does. * The `did:web` projection is the ceremony's own pre-entry PUT there, since a * ladder-signed entry writes `did.jsonl` alone. * * The removal entry publishes conditionally on the log this call read, so a * concurrent enrollment landing in between is never erased by the revocation * (nor the revocation by it): the loser re-runs and rebases its entry on the * winner's head (see `withLogConflictRetry`). * * @param options {object} * @param options.idStore {WebvhIdStore} * @param options.signer {AccountLogSigner} who signs the removal entry: the * REVOKING client's own did:webvh update-key seeds, or the acting * credential's ladder seed * @param [options.projectionStore] {object} an `id`-collection store the * caller may write through (a transient session's, bound to its generation * delegation; an enrolled client's own root-invoking store). Supplied, the * post-removal `did:web` projection is PUT through it immediately BEFORE * the removal entry publishes, which is what keeps a ladder-signed removal * from leaving `did.json` naming the revoked client. Best-effort: a failed * PUT is warned and the removal proceeds. Omitted, the ladder arm leaves * the projection to the next visit's `ensureDidWebProjection` and the * client arm publishes it after the entry, as before * @param options.revokedClient {RevokedClientKeys} the revoked client's * public halves; an `updateKeyMultibase` the log does not authorize (stale, * or the client's staged key) is re-derived from the log * @param [options.knownLatentHashes] {string[]} standing latent commitments * the caller vouches for (the recovery registry's update-key hashes), * excluded from the staged-hash attribution * @param [options.expectedDid] {string} the account DID the log must resolve * to, from the caller's stored account pointer. The read is checked against * the store's own chain-head pin, which advances to what this entry * publishes * @returns {Promise<{ did: string, doc: DIDDoc, log: DIDLog }>} the * account's DID, its resolved document AFTER the edit -- what the roster * rotation that follows resolves its remaining recipients from, so the * caller needs no re-fetch of the log it just extended -- and the post-edit * log itself, from which that rotation's controller view is built (the * revocation cascade's post-edit anchoring). On the idempotent no-op path * these are the already-published document and log, which state the same * thing. */ export declare function revokeWebvhClient(options: { idStore: WebvhIdStore; signer: AccountLogSigner; projectionStore?: Pick; revokedClient: RevokedClientKeys; knownLatentHashes?: string[]; expectedDid?: string; }): Promise<{ did: string; doc: DIDDoc; log: DIDLog; }>; /** * The update keys and committed hashes that belong to the account's SURVIVING * enrolled clients -- what no credential retirement may ever strike, however * an attribution walk came by them. * * A ceremony that retires several credentials in one entry resolves each one's * rungs from the log, and a mis-anchored walk can land on an enrolled client's * key instead. Striking that key is silent and unhealable: the client keeps * its verification methods and its roster wrap, and simply can never extend * the account log again. So the protection is structural rather than a * property of the walk. Every client the document lists under * `capabilityInvocation` contributes its active update key (recovered by the * same attribution the listing performs), that key's carry-over hash, and its * staged hash where the log attributes one. An ambiguous staged attribution * protects every candidate, since over-protecting only leaves a rung standing * while under-protecting destroys a client. * * A client whose ACTIVE update key the listing cannot attribute at all is a * hole in that reasoning: it contributes nothing, so nothing of its would be * protected. Rather than protect a guess, the helper names it on `ambiguous` * and the caller withholds the whole strike. The same shape already disables * a row's disconnect in the clients surface. * * @param options {object} * @param options.log {DIDLog} a resolved, caller-verified log, read BEFORE * the entry is built * @param [options.retiredVmIds] {string[]} verification-method ids the entry * is retiring. A client whose marked `keyAgreement` method is among them is * not surviving and contributes nothing. Credential-class members are never * client-marked, so on today's ceremonies this list never matches; the * parameter is what keeps that an assertion rather than an assumption * @param [options.derivedLatentHashes] {string[]} standing latent * commitments a log walk claimed for the retiring credentials. Excluded * from the staged-hash attribution, so a retiring credential's rung * committed beside a client's staged hash cannot make that attribution * ambiguous and get itself protected -- EXCEPT the hash the decision-0007 * position names as a client's staged hash, which no walk's claim may * prune. A walk anchored on a member that names a client's staged hash * (any update-key holder can publish such a member) claims exactly that * hash, and an exclusion that honored the claim would strike the client's * staged commitment and report the credential as cleanly retired. The * exemption is what makes "whatever the walk claimed" true * @returns {Promise<{ keys: Set, hashes: Set, * ambiguous: string[] }>} */ export declare function survivingClientKeyProtection({ log, retiredVmIds, derivedLatentHashes }: { log: DIDLog; retiredVmIds?: string[]; derivedLatentHashes?: string[]; }): Promise<{ keys: Set; hashes: Set; ambiguous: string[]; }>; //# sourceMappingURL=revokeClient.d.ts.map