import type { DIDDoc, DIDLog, Signer, VerificationMethod } from '@interop/did-method-webvh'; import { type ResourceLogHeadPin, type ResourceLogPinStore } from '@interop/vh-resource-log'; import type { RelationMembership } from './mergeMethods.js'; import type { DidWebKeyMap } from './didWeb.js'; export { assertPublishedLogDid } from './verifyLog.js'; export { updateKeyMultibase } from './updateKeyMultibase.js'; /** * The Space-side seam this module reads and writes through: the world-readable * `id` collection (the DID log and document) and the private `key-map` * collection's `keys.json`. A wallet app's own remote-store class satisfies * the shape structurally -- no adapter needed. */ export interface WebvhIdStore { /** * The raw text body of an `id` collection resource (the JSON-Lines log) * together with its ETag validator, or `undefined` when it is not published. * The `etag` is the compare-and-swap token a ceremony hands back as * {@link putIdResource}'s `ifMatch`; it is absent against a backend that does * not version resources, in which case the ceremony's publish degrades to an * unconditional write. */ getIdResourceRaw(options: { resourceId: string; }): Promise<{ text: string; etag?: string; } | undefined>; /** * The parsed JSON body of an `id` collection resource (the DID document), or * `undefined` when it is not published. */ getIdResource(options: { resourceId: string; }): Promise; /** * Writes (upserts) an `id` collection resource, optionally conditionally: * `ifMatch` writes only if the resource's current ETag matches (an * update-if-unchanged), `ifNoneMatch` writes only if the resource is absent * (a create-if-absent). With neither the write is unconditional. * * Contract: a failed precondition MUST surface as an error whose `name` is * `'PreconditionFailedError'` (was-client's class; any implementation may * throw its own error carrying that name), which this module maps to * {@link WebvhLogConflictError}. * * The returned `etag` is the new validator of the resource just written -- * the `ifMatch` token for the next entry built on it, so a ceremony that * publishes can hand a compare-and-swap-capable head to the stage after it * rather than re-reading the log it just wrote. It is absent against a * backend that does not version resources, and an implementation that * resolves `void` is accepted verbatim (the head it produced then carries * no ETag and the next publish degrades to an unconditional write). */ putIdResource(options: { resourceId: string; content: object | string; contentType?: string; ifMatch?: string; ifNoneMatch?: boolean; }): Promise<{ etag?: string; } | void>; /** * The parsed `keys.json` body together with its ETag validator, or * `undefined` when the map is not written yet. Optional: a store that does * not offer it keeps the pre-convergence behavior -- the genesis' rewrite * has no served map to re-read, so a lost precondition propagates, and the * adopt branch backfills no `webvh` block. */ getKeyMapRaw?(): Promise<{ content: unknown; etag?: string; } | undefined>; /** * Writes `keys.json` in the private `key-map` collection, under the write * precondition the caller states: `ifNoneMatch` for the KMS stage's * create-if-absent write, `ifMatch` for the genesis' rewrite of the map * that write produced. Neither stated, the write is unconditional. * * The returned `etag` is the new validator of the resource just written -- * the `ifMatch` token for the rewrite built on it, so the stage that * records the KMS binding hands a compare-and-swap-capable map to the * stage that adds the account DID. It is absent against a backend that * does not version resources, and an implementation that resolves `void` * is accepted verbatim (the rewrite then degrades to unconditional). */ putKeyMap(options: { content: object; ifMatch?: string; ifNoneMatch?: boolean; }): Promise<{ etag?: string; } | void>; /** * The chain-head pin for the log this store serves: the caller's keyed * {@link ResourceLogPinStore} plus the slot this log occupies in it * (`accountLogPinId({ spaceId })` for the account log; a store over * another collection's `did.jsonl` derives its own slot the same way). A * property of the store rather than an argument of every read and publish, * so no ceremony can read the log unpinned or publish an entry without * advancing the pin: {@link readPublishedLog} refuses a served rollback, * fork, or SCID / method switch against it, and {@link putLogResource} * advances it to every log this store publishes. The constructors derive * the slot from the collection they serve, so no caller pairs a store with * the wrong slot. */ pin: { store: ResourceLogPinStore; logId: string; }; } /** * The Multikey verification-method type the did:webvh data model uses for both * the Ed25519 (authentication/assertionMethod/capability*) and X25519 * (keyAgreement) keys. The same key material and multibase are carried as by * the 2020 suite types, only `type` and `@context` change, and credential * verifiers verify `Ed25519Signature2020` / `eddsa-rdfc-2022` proofs against * it. */ export declare const MULTIKEY_VM_TYPE = "Multikey"; /** * The verification-method type a hash commitment to a key-agreement key is * published under, in place of the key itself * ({@link keyAgreementCommitment}). It carries `publicKeyCommitment` rather * than `publicKeyMultibase`, so a reader keying on `Multikey` never mispairs * a commitment with real key material. Wire-level and permanent. */ export declare const MULTIKEY_COMMITMENT_VM_TYPE = "MultikeyCommitment"; /** * The context URL defining the `MultikeyCommitment` and `publicKeyCommitment` * terms. Every account document carries it, so a commitment verification * method is a defined term rather than a bare JSON property. The value is * byoe-context's `VOCAB_CONTEXT_URL` (`https://w3id.org/byoe/v1`) -- the * package whose contexts map backs the bundled document loader -- so the URL * written into document bytes can never drift from the loader's coverage. */ export declare const BYOE_CONTEXT_URL = "https://w3id.org/byoe/v1"; /** * The client-held did:webvh update-key material: 32-byte Ed25519 seeds. * `pendingStagedSeed` is present only mid-rotation (minted and persisted * before the log entry publishes, promoted to `stagedSeed` after). */ export interface ClientWebvhUpdateKeys { updateSeed: Uint8Array; stagedSeed: Uint8Array; pendingStagedSeed?: Uint8Array; } /** * The public halves of one enrolled client's key set, as they appear in the * document: the Ed25519 signing key (`z6Mk...`) and its X25519 key-agreement * twin (`z6LS...`). */ export interface WebvhClientKeys { signingKeyMultibase: string; keyAgreementKeyMultibase: string; } /** * The CONTROLLER MARKER an enrolled client's `keyAgreement` verification * method carries: the client's own did:key, rather than the account DID every * other method carries. It is the document's one statement of which signing * key a published key-agreement key belongs to, so a reader pairs a client * with its key-agreement key by reading the document instead of deriving a * twin -- and it agrees with the DID half of the client's roster kid. * * Only a CLIENT's key-agreement method is marked. Signing keys are never * marked (a did:key controller on a proof key breaks controller-based proof * verification), and a recovery code's key-agreement method is deliberately * left unmarked, so client listings and revocation removals never match it. * * @param options {object} * @param options.signingKeyMultibase {string} the client's Ed25519 signing * key, as the document publishes it * @returns {string} */ export declare function clientKeyAgreementController({ signingKeyMultibase }: { signingKeyMultibase: string; }): string; /** * The multibase of an Ed25519 signing key's canonical X25519 twin. Delegates * to was-client's `x25519RecipientFromDidKey`, the one rule for this * derivation, which also refuses a multibase that is not an Ed25519 key (no * twin exists for anything else). * * Nothing DERIVES a client's published key-agreement key with it -- the * listing reads that off the document's controller marker. It is the * canonicality rule instead: {@link markedVerificationMethodPair} runs it at * every site that writes the marker, and the enrollment ceremony runs it * early enough to refuse a connect code before an approver ever sees it, so * the marker's claim ("this key-agreement key belongs to that signing key") * is true of every method a wallet publishes. * * @param options {object} * @param options.signingKeyMultibase {string} * @returns {string} */ export declare function keyAgreementTwinMultibase({ signingKeyMultibase }: { signingKeyMultibase: string; }): string; /** * The hash commitment of a key-agreement key, as a document publishes it in * place of the key itself for a low-entropy-derived standing unlock * credential (the `MultikeyCommitment` verification-method convention): a * bare multihash -- sha2-256 over the key's DECODED multikey bytes -- encoded * base64url-no-pad, with no multibase prefix. The multihash header keeps the * algorithm self-describing, so a verifier decodes rather than re-encodes. * Deliberately independent of the `nextKeyHashes` rule, which keeps its own * base58btc encoding. * * What the commitment provides is the document-anchored integrity check the * roster's recipient resolver runs against a roster-carried key, plus * non-disclosure of the key material itself. It does not reduce offline * guessing exposure: under a fixed KDF salt a commitment costs one extra * sha256 per guess, so that exposure belongs to the standing-credential model * and its KDF choice rather than to this encoding. Wire-level and permanent. * * @param options {object} * @param options.keyAgreementKeyMultibase {string} * @returns {Promise} */ export declare function keyAgreementCommitment({ keyAgreementKeyMultibase }: { keyAgreementKeyMultibase: string; }): Promise; /** * Whether a candidate key-agreement key is the one a published commitment * commits to. Verification DECODES: the commitment's multihash header names * the algorithm, the candidate's decoded multikey bytes are hashed with it, * and the digests are compared -- so a future algorithm is an additive change * rather than a format change. A commitment that does not decode, or names an * algorithm with no implementation here, simply does not match. * * @param options {object} * @param options.commitment {string} a published `publicKeyCommitment` * @param options.keyAgreementKeyMultibase {string} the candidate key * @returns {boolean} */ export declare function commitmentMatchesKey({ commitment, keyAgreementKeyMultibase }: { commitment: string; keyAgreementKeyMultibase: string; }): boolean; /** * A pre-decoded matcher over a SET of published commitments, for a caller * that checks many candidate keys against the same document (the user key * roster's recipient resolver). Each commitment is decoded once up front -- * duplicates collapsed, one that does not decode or names an algorithm with * no implementation here dropped, the {@link commitmentMatchesKey} non-match * contract -- and each candidate is hashed once per call, so resolving N * roster entries against K commitments costs N hashes rather than N*K * decode-and-hash passes. * * @param options {object} * @param options.commitments {string[]} the published `publicKeyCommitment` * values * @returns {function} `(keyAgreementKeyMultibase: string) => boolean` */ export declare function commitmentMatcher({ commitments }: { commitments: string[]; }): (keyAgreementKeyMultibase: string) => boolean; /** * The one builder of a client's published verification-method pair: the * account-controlled Ed25519 signing method first, then the X25519 * key-agreement method under the controller marker * ({@link clientKeyAgreementController}). Every site that writes the marker * -- the genesis assembly, the enrollment add entry, and the recovery * continuation's add-and-retire entry -- builds through it, so the permanent * document convention is applied in one place rather than remembered at each * site. * * It refuses a pair whose key-agreement key is not the canonical X25519 twin * of the signing key, which is what makes the marker's claim true wherever it * is written: no public entry point can publish a marker the account cannot * back. * * @param options {object} * @param options.controller {string} the account's controller id, used both * as the methods' id prefix and as the signing method's controller (the * `{SCID}` template at genesis, the resolved DID afterwards) * @param options.signingKeyMultibase {string} * @param options.keyAgreementKeyMultibase {string} * @returns {VerificationMethod[]} the signing method, then the marked * key-agreement method */ export declare function markedVerificationMethodPair({ controller, signingKeyMultibase, keyAgreementKeyMultibase }: { controller: string; signingKeyMultibase: string; keyAgreementKeyMultibase: string; }): VerificationMethod[]; /** * The add-side twin of the revocation module's `clientRemovalFields`: what * ONE enrolled client contributes to the account document, stated once. * The marked verification-method pair ({@link markedVerificationMethodPair}) * and the relation membership every enrolled client publishes -- its signing * method under all four signing relations (`authentication`, * `assertionMethod`, `capabilityInvocation`, `capabilityDelegation`) and its * key-agreement twin under `keyAgreement`. The four add sites (the genesis * assembly, the enrollment add entry, the self-enrollment add entry, and the * recovery continuation's add-and-retire entry) take the bundle from here, so * a relation can no longer be missed at one of them: a client published * without `assertionMethod` cannot sign roster appends, and one published * without `capabilityInvocation` cannot make any WAS request, and both * surface only later, at the server. * * @param options {object} * @param options.controller {string} the account's controller id (the * `{SCID}` template at genesis, the resolved DID afterwards) * @param options.signingKeyMultibase {string} * @param options.keyAgreementKeyMultibase {string} * @returns {{ methods: VerificationMethod[], relations: Required }} * the marked pair, and the ids each of the five relations gains, as * `mergeVerificationMethods` takes them */ export declare function clientAdditionFields({ controller, signingKeyMultibase, keyAgreementKeyMultibase }: { controller: string; signingKeyMultibase: string; keyAgreementKeyMultibase: string; }): { methods: VerificationMethod[]; relations: Required; }; /** * The refusal behind {@link markedVerificationMethodPair}, standing alone so a * ceremony can run it BEFORE its first read: a client key set whose * key-agreement key is not the signing key's canonical X25519 twin can never * publish, so refusing it up front costs nothing, while letting the * pair travel to the marked-pair build would spend a reveal-and-commit entry * (and fire the persist-before-publish seam) on a continuation that can only * ever throw at the add entry. * * @param options {object} * @param options.signingKeyMultibase {string} * @param options.keyAgreementKeyMultibase {string} * @returns {void} */ export declare function assertCanonicalClientKeys({ signingKeyMultibase, keyAgreementKeyMultibase }: { signingKeyMultibase: string; keyAgreementKeyMultibase: string; }): void; /** * The one builder of the ladder VM's published verification method -- the * STABLE SIBLING key a standing credential derives from its ladder seed * (`@interop/wallet-core/unlock`, `ladderVmKeyMultibase`), published for as * long as that credential stands and co-resident with whatever clients the * account has enrolled. The shape is forced, not * preferred: @interop/zcap's `isController` flat-compares the delegating VM's * `controller` string against the parent capability's controller (which the * server synthesizes as the account did:webvh), and only an * `#` id dereferences through the server's fragment * resolver -- so the controller must be the account id and the fragment must * be the key multibase. * * The method is listed under `assertionMethod` and `capabilityDelegation` * ONLY -- no `authentication`, no `capabilityInvocation`, no `keyAgreement` * twin, and no marker property. Recognition is by that relation asymmetry * (`ladderVmIds`, in the shared account-document readers): a * `capabilityDelegation` member absent from `capabilityInvocation` is the * ladder VM, which also keeps it structurally out of every client listing. * * Because the key is derived, a reinstall republishes the SAME key under the * SAME id, and a still-unexpired delegation it signed resumes verifying the * moment the method returns -- so delegation revocation, not VM removal, is * the terminal remedy for ladder-signed delegations. * * @param options {object} * @param options.controller {string} the account's controller id (the * `{SCID}` template at genesis, the resolved DID afterwards) * @param options.publicKeyMultibase {string} the ladder VM's Ed25519 key * @returns {VerificationMethod} */ export declare function ladderVerificationMethod({ controller, publicKeyMultibase }: { controller: string; publicKeyMultibase: string; }): VerificationMethod; /** * The `webvh` block added to `keys.json` v2, a sibling of the did:web * relationship map. Absent block = a record written before did:webvh hosting; * everything degrades to did:web behavior, no format-version bump (additive * convention). It carries only the published DID: the update keys are * client-held seeds, never recorded in a Space-hosted resource. */ export interface DidWebvhBlock { did?: string; } /** * `keys.json` v2: the KMS binding map plus the optional `webvh` block. The * parse/guard tolerates and preserves the block, so a round-trip through the * KMS-authentication stage never strips it. */ export type DidWebKeyMapV2 = DidWebKeyMap & { webvh?: DidWebvhBlock; }; /** * What the KMS-authentication stage hands the genesis: the key map to fold * into the genesis entry, and the `keys.json` ETag the stage's own write * produced, which the genesis' rewrite carries as its `ifMatch`. The ETag is * absent when the stage wrote nothing (it adopted a served map) or when the * backend versions no resources, and the rewrite then degrades to an * unconditional write. */ export interface KmsAuthenticationBinding { keys: DidWebKeyMapV2; etag?: string; } /** * The `did:webvh:{SCID}::space::` controller * template, with the literal `{SCID}` placeholder the library replaces at * creation. The host segment percent-encodes a port (`localhost:8080` becomes * `localhost%3A8080`), matching the library's `toDidDomainComponent`. The * collection defaults to the account log's `id` collection; a client-annex * generation's log passes its own `gen-` generation id. * * @param options {object} * @param options.wasServerUrl {string} * @param options.spaceId {string} * @param [options.collectionId] {string} the collection holding `did.jsonl` * (defaults to the `id` collection) * @returns {string} */ export declare function didWebvhControllerTemplate({ wasServerUrl, spaceId, collectionId }: { wasServerUrl: string; spaceId: string; collectionId?: string; }): string; /** * Mints a fresh pair of client-held update-key seeds (active + staged) for a * brand-new did:webvh log. The caller owns persistence: these seeds are the * only update authority the log will ever accept, so they must be persisted * client-local before {@link ensureDidWebvh} publishes anything. * * @returns {ClientWebvhUpdateKeys} */ export declare function mintClientWebvhUpdateKeys(): ClientWebvhUpdateKeys; /** * Bridges a client-held update-key seed to the did:webvh `Signer` interface via * the library's `signerFromExternalKey`. The only wallet-side seam is the shape * adapter: the key pair's `signer().sign({ data })` matches the factory's * `sign({ data })` bridge exactly, so the proof-value multibase encoding and * the load-bearing `did:key:#` verification-method id (which the * resolver matches against the entry's authorized `updateKeys`) are owned by * the library, not duplicated here. * * @param options {object} * @param options.seed {Uint8Array} the 32-byte update-key seed * @returns {Promise} */ export declare function updateKeySigner({ seed }: { seed: Uint8Array; }): Promise; /** * What a create path hands back: the log, its `did:web` projection, and the * resolved state `createDID` already returned. The three resolved members are * what let a caller that publishes this log assemble a * {@link PublishedWebvhLog} for the head it just wrote -- pairing them with * the publish's own ETag -- rather than resolving the log a second time. */ export interface CreatedWebvhLog { log: DIDLog; webDoc: object; did: string; /** * The resolved document, detached from the genesis entry's own `state`, so * it aliases exactly as a read-side head's document does. */ doc: DIDDoc; updateKeys: string[]; nextKeyHashes: string[]; } /** * Builds a genesis entry's `nextKeyHashes`: the active update key's own * carry-over hash beside the staged key's (the carry-over convention in the * module doc). The resolver checks every later entry's re-stated `updateKeys` * against these commitments, so without the carry-over hash no non-rotating * entry (an enrollment commit, a self-enrollment's reveal-and-commit) could * ever follow the genesis. One builder for both genesis flavors (the * founding-client genesis and the ladder-anchored one), so the convention * cannot be dropped from one and kept in the other. * * @param options {object} * @param options.activeKeyMultibase {string} the genesis `updateKeys` member * @param options.stagedKeyMultibase {string} the prerotation staged key * @returns {Promise<[string, string]>} the carry-over hash, then the * staged hash */ export declare function genesisNextKeyHashes({ activeKeyMultibase, stagedKeyMultibase }: { activeKeyMultibase: string; stagedKeyMultibase: string; }): Promise<[string, string]>; /** * Creates the one-entry LADDER-ANCHORED did:webvh log -- the * credential-anchored genesis of an account with zero enrolled clients, * anchored on the minting credential's ladder alone. The document is the * ladder-anchored assembly's ({@link assembleWebvhVerificationMethods}): the * ladder VM under `assertionMethod` and `capabilityDelegation` only, the * credential's key-agreement entry as the sole `keyAgreement` member (folded * into genesis -- no enrolled client exists to run the separate bind entry), * and nothing invocable. When the wallet keeps a KMS, `didWebKeys` folds the * KMS-held authentication key in under `authentication` only, exactly as on * the enrolled-client flavor. * * The caller supplies the ladder-derived update authority: rung 0's key as * the sole `updateKeys` member, `nextKeyHashes` = [hash(rung 0), * hash(rung 1)] (built with {@link genesisNextKeyHashes}) -- the active * rung's own carry-over hash plus the staged rung, the carry-over half being * what the first self-enrollment's reveal-and-commit entry * (re-stating `updateKeys` containing rung 0) requires -- and rung 0's * signer. `portable` stays true, the account * log's standing value. The unlock layer's `createLadderAnchoredAccountLog` * derives all of that from the ladder seed and is the ordinary caller; this * export is the document machinery. * * @param options {object} * @param options.wasServerUrl {string} * @param options.spaceId {string} * @param [options.didWebKeys] {DidWebKeyMap} absent on a KMS-less genesis * @param options.ladderVmKeyMultibase {string} the credential's ladder VM * @param options.credentialKeyAgreementMethod {VerificationMethod} the * credential's key-agreement entry (commitment or verbatim), built over the * `{SCID}` controller template ({@link didWebvhControllerTemplate}) * @param options.updateKeyPublicKeyMultibase {string} ladder rung 0's key * @param options.nextKeyHashes {string[]} [hash(rung 0), hash(rung 1)] * @param options.signer {Signer} ladder rung 0's signer * @returns {Promise} */ export declare function createLadderAnchoredWebvhLog({ wasServerUrl, spaceId, didWebKeys, ladderVmKeyMultibase, credentialKeyAgreementMethod, updateKeyPublicKeyMultibase, nextKeyHashes, signer }: { wasServerUrl: string; spaceId: string; didWebKeys?: DidWebKeyMap; ladderVmKeyMultibase: string; credentialKeyAgreementMethod: VerificationMethod; updateKeyPublicKeyMultibase: string; nextKeyHashes: string[]; signer: Signer; }): Promise; /** * Thrown when a conditional publish of `did.jsonl` lost a race to a concurrent * ceremony: the log moved on between the read this ceremony built its entry on * and the PUT that would have appended it. The `cause` is the store's * precondition error. A ceremony that meets one either re-runs (rebasing its * entry on the new head -- what {@link withLogConflictRetry} does) or refuses, * but it never overwrites the winner's entry. */ export declare class WebvhLogConflictError extends Error { constructor(message?: string, options?: { cause?: unknown; }); } /** * Re-runs `run` when it fails with a {@link WebvhLogConflictError}, up to three * total attempts, then rethrows. The retry IS the rebase: every ceremony in * this module re-reads the published head and rebuilds its entry on it, and * every one of them detects its own completion from durable state alone, so a * naive re-run after a lost race appends on top of the winner instead of over * it. A ceremony whose preconditions no longer hold after the re-read (the * retrying client was itself revoked, the staged key it was about to reveal is * no longer committed) surfaces its own typed refusal instead of looping. * * @param run {Function} the ceremony body, re-invokable from the top * @returns {Promise<*>} whatever the ceremony returns */ export declare function withLogConflictRetry(run: () => Promise): Promise; /** * The threaded-head attempt every entry writer that saved a read shares. A caller that * already read and verified the head under this same pin slot gets ONE * attempt built on it; a lost compare-and-swap there says only that the head * is stale, so the conflict retry re-reads under the pin with its whole * budget. Every other failure is the caller's. The threaded attempt is EXTRA * rather than one of the retry's three, so a caller who saved a read is left * with the same conflict budget as one who did not. * * @param options {object} * @param [options.published] {PublishedWebvhLog} the caller's threaded head * @param options.attempt {Function} one attempt of the ceremony, taking the * head to build on (absent, the attempt reads for itself) * @returns {Promise} */ export declare function withThreadedHeadOnce({ published, attempt }: { published?: PublishedWebvhLog; attempt: (published?: PublishedWebvhLog) => Promise; }): Promise; /** * PUTs the serialized log to `did.jsonl`, forwarding the conditional-write * preconditions and mapping a failed one to {@link WebvhLogConflictError}. The * single place that mapping exists: the store seam is app-implemented, so the * precondition error is matched on `err.name` rather than by `instanceof`, * keeping the check implementation-agnostic. With neither precondition the PUT * is unconditional -- the degradation on a backend that serves no ETags. * * A successful write advances the store's chain-head pin to the log just * published, so a host rolling the log back straight afterwards is refused * on the next read. The write and the advance are one function on purpose: * separating them is what leaves a pin standing behind an entry this client * itself published. The pin write still lands after the PUT, not with it, so * a client torn between the two holds a pin behind its own entry; that * one-request window is what `BuiltOnHeadNotReachedError` still guards on a * resume. Every log publish in this library (the create, the rotation, every * ceremony entry) runs through here. * * @param options {object} * @param options.store {object} the seam's `putIdResource` and its `pin` * @param options.log {DIDLog} * @param [options.ifMatch] {string} publish only if the log is unchanged * @param [options.ifNoneMatch] {boolean} publish only if the log is absent * @returns {Promise<{ etag?: string }>} the new validator of the log just * written, for a stage building its entry on this head; absent against a * backend that serves no ETags */ export declare function putLogResource({ store, log, ifMatch, ifNoneMatch }: { store: Pick; log: DIDLog; ifMatch?: string; ifNoneMatch?: boolean; }): Promise<{ etag?: string; }>; /** * THE POSTAMBLE: publishes `did.jsonl` -- the log only, never `did.json` (a * caller writing through a bridge delegation is authorized for nothing else) * -- and advances the store's chain-head pin to what it just published, so a * host rolling the log back straight afterwards is refused on the next read. * * So an entry published here leaves the `did:web` projection standing at * whatever it said before: a ceremony whose entry REMOVES inventory (a * removal, a retirement) must republish the projection itself before the * entry lands, and a projection left behind by one that did not is mended by * `ensureDidWebProjection` at the next visit holding a writer for the `id` * collection (a controller-invoking client, or a transient visit under its * generation delegation). * The publish is conditional on the read the entry was built on; a lost race * surfaces as a {@link WebvhLogConflictError} (the mapping lives in * {@link putLogResource}). * * The pin advance is {@link putLogResource}'s own, since the pin is a member * of the store; this name is the ladder-signed ceremonies' statement that * their publish is the log-only one. * * @param options {object} * @param options.store {object} the seam's `putIdResource` and its `pin` * @param options.log {DIDLog} the log this entry produced * @param [options.ifMatch] {string} publish only if `did.jsonl` is unchanged * @returns {Promise<{ etag?: string }>} the new validator of the log this * entry just published, for a stage building on the post-entry head */ export declare function publishEntryPinned({ store, log, ifMatch }: { store: Pick; log: DIDLog; ifMatch?: string; }): Promise<{ etag?: string; }>; /** * Publishes an already-created log: PUT `did.jsonl` (`text/jsonl`) then PUT * `did.json` from `webDoc` (`application/did+json`, adopting the webvh * projection). Both land in the `id` collection, whose collection-level * `PublicCanRead` policy (set at provisioning) makes them world-readable, so * the publish tail no longer sets per-resource policies. The shared publish * tail of the create and rotate paths. * * The log PUT carries the caller's precondition (`ifMatch`, the ETag of the * read the entry was built on, or `ifNoneMatch` for a create), so a ceremony * that lost a race to a concurrent one fails with * {@link WebvhLogConflictError} instead of silently erasing the winner's entry. * The `did.json` projection PUT stays unconditional by design: it runs only * after the log's compare-and-swap was WON, so it is already serialized behind * that win; the log is the source of truth and the projection a derived cache; * and {@link concludeWithPublishedLog} re-derives and republishes the * projection from the resolved log on the no-op path of every ceremony that * reaches this function, so a torn publish on one of those paths is healed by * the next run of the same ceremony. That reach is the controller-invoking * paths alone; `ensureDidWebProjection` is what mends a projection a * ladder-signed entry ({@link publishEntryPinned}) left behind. * * @param options {object} * @param options.idStore {WebvhIdStore} * @param options.log {DIDLog} * @param options.webDoc {object} * @param [options.ifMatch] {string} publish only if `did.jsonl` is unchanged * @param [options.ifNoneMatch] {boolean} publish only if `did.jsonl` is absent * @returns {Promise<{ etag?: string }>} the LOG's new validator, never the * projection's: the projection is a derived cache, and only the log's ETag * is a precondition anything is built on */ export declare function publishWebvhLog({ idStore, log, webDoc, ifMatch, ifNoneMatch }: { idStore: WebvhIdStore; log: DIDLog; webDoc: object; ifMatch?: string; ifNoneMatch?: boolean; }): Promise<{ etag?: string; }>; /** * The shared guard-and-publish tail of every ceremony that extends the log * through `updateDID`: the parallel `webDoc` must be there (it is, whenever * `alsoKnownAsWeb` was passed), then log and projection are published under the * caller's compare-and-swap token. * * @param options {object} * @param options.idStore {WebvhIdStore} * @param options.updated {object} the `updateDID` result * @param options.updated.log {DIDLog} * @param [options.updated.webDoc] {object} * @param [options.ifMatch] {string} the ETag of the read this entry was built * on * @returns {Promise<{ etag?: string }>} the log's new validator */ export declare function publishUpdatedLog({ idStore, updated, ifMatch }: { idStore: WebvhIdStore; updated: { log: DIDLog; webDoc?: object; }; ifMatch?: string; }): Promise<{ etag?: string; }>; /** * Writes `keys.json` v2: the KMS binding plus the `webvh` block. Exported for * the ladder-anchored ensure, whose create path records the account DID the * same way the enrolled-client one does. * * The body is CONSTRUCTED from the two members the map carries rather than * spread from the caller's, so a legacy `keyAgreement` binding a stored map * still holds is dropped by this rewrite. * * This is the genesis' rewrite of the map the KMS stage created one stage * earlier, so it carries that write's ETag as its `ifMatch`. A caller holding * no ETag (a backend that versions nothing) writes unconditionally. * * A lost precondition CONVERGES rather than propagating, since the genesis * entry has already published by the time this runs and a bookkeeping resource * must not fail a ceremony standing behind it: the served map is re-read, a * map already naming this DID under this binding is left alone, and anything * else is rewritten once under the served ETag. A second lost precondition * propagates. Without the store's optional read there is nothing to converge * on, and the first failure propagates. * * @param options {object} * @param options.idStore {WebvhIdStore} * @param options.didWebKeys {DidWebKeyMap} the binding to record, which on * the create path is the one this run published in the genesis entry * @param options.webvh {DidWebvhBlock} * @param [options.ifMatch] {string} the ETag the KMS stage's own write * returned * @returns {Promise} */ export declare function writeKeysJson({ idStore, didWebKeys, webvh, ifMatch }: { idStore: WebvhIdStore; didWebKeys: DidWebKeyMap; webvh: DidWebvhBlock; ifMatch?: string; }): Promise; /** * Records the account DID into a `keys.json` carrying a KMS binding and no (or * a stale) `webvh` block -- the state a run torn between its genesis entry and * its rewrite leaves behind. It is the adoption path's half of that rewrite, * and the binding comes from the SERVED map: the run that published the log * recorded it, while this run's own map may name a key that log never * published. * * A no-op when the store offers no read, when the served map carries no * binding, and when it already names this DID. * * @param options {object} * @param options.idStore {WebvhIdStore} * @param options.did {string} the DID the adopted log resolves to * @returns {Promise} */ export declare function backfillKeyMapWebvhBlock({ idStore, did }: { idStore: WebvhIdStore; did: string; }): Promise; /** * The verified state of the published log: the log itself, the resolved DID * and document, and the effective update-key parameters (the authorized * `updateKeys` and the standing `nextKeyHashes` commitments). */ export interface PublishedWebvhLog { log: DIDLog; did: string; doc: DIDDoc; updateKeys: string[]; nextKeyHashes: string[]; /** * The `did.jsonl` ETag observed by the read that produced this state -- the * `ifMatch` token for the publish of any entry built on it. Absent against a * backend that serves no ETags, where the publish degrades to unconditional. */ etag?: string; } /** * Reads and resolves the published `did.jsonl`, or returns `undefined` when * the log is not published. A log that exists but fails to resolve throws -- * a published-but-broken log is never silently re-created over. * * Given an `expectedDid`, a log resolving to any other DID is refused rather * than built on: the substituted-account check `verifyAccountLog` runs on the * world-readable read, applied here to the ceremony's own read. Callers that * hold the account pointer (or an earlier read of the same log, mid-ceremony) * pass it; a caller discovering the DID from the log itself cannot. * * The resolved log takes the same chain-head continuity check * `verifyAccountLog` runs, against the store's own pin, through literally the * same seam and refusal class: a served log that is a rollback, a fork, or an * SCID/method switch relative to the pinned head is refused with a * {@link ResourceLogContinuityError} rather than built on, and the pin is * established at first contact and advanced only by a log that verifies past * it. This is what stops a host from feeding a ceremony a valid PREFIX of the * real log -- same SCID, same DID, resolves cleanly -- and having the ceremony * republish the truncated history plus one entry as durable state. A * `rollback` is the one reason that may be nothing worse than replication lag, * exactly as on a governed resource log; nothing rolled back is ever adopted * here either way. An ABSENT log under a held pin is not "not yet published" * but a full truncation of a history this client has already seen, so it is * refused as a `rollback` instead of read as `undefined`. * * @param options {object} * @param options.idStore {Pick} * the read half of the seam plus its pin, so a caller holding a narrower * store (a bridge delegation's read + PUT pair) passes it directly * @param [options.expectedDid] {string} the DID the log must resolve to * @param [options.absentUnderPin] {'refuse' | 'absent'} what an absent log * under a held pin reads as: `refuse` (the default, the account log's * rule above) throws the `rollback` refusal; `absent` resolves `undefined` * and leaves the pin standing, for a log that is deleted by design (an * annex generation). A served log that falls behind the pin stays refused * under both * @returns {Promise} */ export declare function readPublishedLog({ idStore, expectedDid, absentUnderPin }: { idStore: Pick; expectedDid?: string; absentUnderPin?: 'refuse' | 'absent'; }): Promise; /** * {@link readPublishedLog} for the ceremonies whose premise is a log that * already exists: an absent `did.jsonl` is a refusal rather than a state to * branch on, so the caller gets a `PublishedWebvhLog` or an error. The * `missingMessage` is the caller's own phrasing of what there is nothing to do * against ("nothing to enroll into", "nothing to recover"), since the read * itself cannot know which ceremony is standing on it. * * Every other option, and every check behind them -- the `expectedDid` * refusal and the chain-head pin's rollback / fork / identity-switch * refusals -- is {@link readPublishedLog}'s verbatim. Each attempt of a * conflict-retried ceremony reads for itself, so the continuity check runs on * the read the compare-and-swap publish is conditioned on rather than only on * an orchestrator's pre-read. * * @param options {object} * @param options.idStore {Pick} * @param [options.expectedDid] {string} the DID the log must resolve to * @param [options.missingMessage] {string} the thrown `Error`'s message when * the log is absent * @returns {Promise} */ export declare function readPublishedLogOrThrow({ idStore, expectedDid, missingMessage }: { idStore: Pick; expectedDid?: string; missingMessage?: string; }): Promise; /** * The chain-head pin a log establishes: the genesis entry's method and SCID * plus the head entry's `versionId`. Used where this client is the one that * just published the log and so knows its genesis firsthand -- first contact * should not be left to whatever the host serves back on the next read. * * @param log {DIDLog} * @returns {ResourceLogHeadPin} */ export declare function pinOfLog(log: DIDLog): ResourceLogHeadPin; /** * The head of a log snapshot in the pending record's terms: the genesis * parameters' SCID plus the latest entry's `versionId`. What a * persist-before-publish seam hands its caller as the `builtOnHead` marker. * * @param log {DIDLog} * @returns {{ scid: string, versionId: string }} */ export declare function servedHead(log: DIDLog): { scid: string; versionId: string; }; /** * The shared tail of every ceremony path that has nothing left to append to * the log -- an adoption, a resumed rotation, an already-enrolled no-op, an * already-revoked no-op. All of them used to infer completion from `did.jsonl` * alone, which is a half of the state: {@link publishWebvhLog} writes the log * and its `did:web` projection in two non-atomic PUTs, so a crash between them * leaves a `did.jsonl` that is complete beside a `did.json` that lags it. * * So the projection is re-derived from the resolved log and re-PUT * unconditionally rather than compared first: the write is idempotent and one * request either way, and the resolved log is the source of truth for what the * projection must say. A ceremony that no-ops on the log therefore still heals * a torn earlier publish OF THAT CEREMONY. The projection PUT is deliberately * unconditional here too: healing it is the whole point, and the log it was * derived from is the state this call just read and resolved. * * The reach is what to hold on to: every caller here invokes as the account's * controller, so this heals nothing for the ladder-signed entries that publish * through {@link publishEntryPinned} and write the log alone. Those are mended * by their own ceremony's pre-entry projection PUT and, failing that, by * `ensureDidWebProjection` at the next visit that holds an `id`-collection * writer. * * @param options {object} * @param options.idStore {WebvhIdStore} * @param options.published {PublishedWebvhLog} the resolved published log * @returns {Promise<{ did: string; doc: DIDDoc }>} the published DID and its * resolved document */ export declare function concludeWithPublishedLog({ idStore, published }: { idStore: WebvhIdStore; published: PublishedWebvhLog; }): Promise<{ did: string; doc: DIDDoc; }>; /** * The per-entry EFFECTIVE `updateKeys` / `nextKeyHashes` of a log, with * did:webvh's carry-forward semantics applied (an entry that omits a * parameter inherits the previous entry's value). Shared by the revocation * edit's staged-hash attribution and the enrolled-client listing's * update-key attribution. * * @param log {DIDLog} * @returns {Array<{ updateKeys: string[]; nextKeyHashes: string[] }>} */ export declare function effectiveParameters(log: DIDLog): Array<{ updateKeys: string[]; nextKeyHashes: string[]; }>; /** * The log's current `updateKeys` / `nextKeyHashes` view: the last entry's * effective parameters, or the empty pair for a log with no entries. * * The empty default is a policy choice, not a convenience: it is what makes * an unresolvable log attribute as "no rung standing" rather than throw, so * it is stated here once rather than at each attribution site. * * @param published {object} * @param published.log {DIDLog} * @returns {object} */ export declare function currentLogParameters(published: { log: DIDLog; }): { updateKeys: string[]; nextKeyHashes: string[]; }; /** * Idempotently provisions and publishes the user's did:webvh DID log. A wallet * that keeps a KMS runs it directly after the did:web provisioning (non-fatal) * and supplies the resulting `didWebKeys`; a wallet with no KMS supplies none, * gets the client-keys-only genesis, and no `keys.json` is ever written (the * record exists to bind DID relationships to KMS keys, and there are none). * The anchor is the caller-persisted update-key seeds, so the flow is a * simple probe: * * - `did.jsonl` published: sanity-check that the log's authorized `updateKeys` * still name one of this client's seeds (active, staged, or pending -- a * rotation in flight finalizes separately), adopt the resolved DID, and * (with a key map) write the `keys.json` webvh block if it is missing or * stale. * - `did.jsonl` absent: create the log with the active update key, prerotation * committed to the staged key, publish log + `did.json`, then (with a key * map) record the DID in `keys.json`. * * A published log whose `updateKeys` match none of the seeds is fatal: with * client-held update keys a lost seed is lost update authority, and no KMS * repair path exists by design. * * The create publishes `did.jsonl` create-if-absent, so two concurrent signups * cannot double-create the log: the loser re-runs (see * {@link withLogConflictRetry}) and takes the adoption path against the * winner's log -- adopting it when it holds this client's seeds, and raising * the lost-seed refusal when it does not. * * The probe read is checked against the DID this run expects -- a caller's * `expectedDid`, else the `webvh` block of the `keys.json` it was handed -- and * against the store's chain-head pin, so neither a substituted log nor a * truncated prefix of the real one can be adopted here. One exemption, and it * is the documented first-contact case: an adoption holding neither a * caller-supplied `expectedDid` nor a `keys.json` webvh block legitimately * discovers the DID from the log itself, and its read is what establishes the * pin (trust-on-first-use). On the create path the pin is written to the log * this run just published, since the creator knows the true genesis and first * contact should not be left to the next read. * * @param options {object} * @param options.idStore {WebvhIdStore} * @param options.wasServerUrl {string} * @param options.spaceId {string} * @param [options.didWebKeys] {DidWebKeyMapV2} the parsed keys.json (with any * webvh block) returned by the KMS-authentication stage; absent on a * client-keys-only genesis (no KMS anywhere in the path) * @param [options.keysJsonEtag] {string} the ETag that stage's own write * returned, carried as the `ifMatch` of the rewrite that records the DID * @param options.clientKeys {WebvhClientKeys} this client's published keys * @param options.updateKeys {ClientWebvhUpdateKeys} already persisted * client-local * @param [options.expectedDid] {string} the DID the published log must * resolve to, when the caller holds the account pointer * @returns {Promise<{ did: string }>} */ export declare function ensureDidWebvh(options: { idStore: WebvhIdStore; wasServerUrl: string; spaceId: string; didWebKeys?: DidWebKeyMapV2; keysJsonEtag?: string; clientKeys: WebvhClientKeys; updateKeys: ClientWebvhUpdateKeys; expectedDid?: string; }): Promise<{ did: string; }>; /** * Rotates this client's did:webvh update key (the user-triggered ceremony on * the wallet's settings screen). The staged key is revealed to sign its own * activation and become the sole active update key, a freshly minted staged * key is committed as the new `nextKeyHashes`, and the caller's persisted * seeds roll forward. No KMS and no `keys.json` involvement: the update keys * are client-held, and the published DID does not change. * * The persist-before-publish invariant is load-bearing: the new staged seed is * handed to `persistUpdateKeys` (as `pendingStagedSeed`) and awaited BEFORE the * log entry committing it is published, so no published log can ever depend * on a seed the caller has not persisted. A crash between publish and * finalize is recovered on the next run: the log already sits at the staged * (or pending) key, and the seeds are simply rolled forward locally without * touching it. * * Divergence that is NOT that recoverable case is refused up front, before * anything is persisted or published: the log must still authorize this * client's active update key AND commit its staged key's hash as a next key. * Both are checked here rather than left to the resolver, so a diverged * client fails with a statement of what diverged instead of persisting rolled * seeds and then failing opaquely. * * The entry publishes conditionally on the log this ceremony read, so a * concurrent ceremony's entry is never erased; a lost race re-runs from the top * (see {@link withLogConflictRetry}), which mints and persists a fresh staged * seed again -- the documented cost of a torn rotation, one unused staged key. * * A rotation is a publish, so the read it builds on carries the full check: an * `expectedDid` refuses a substituted log, and the store's chain-head pin * refuses a served history that is a rollback, a fork, or an SCID/method * switch -- without it a host serving a truncated prefix gets this ceremony to * republish the truncation plus its own entry as the log's durable state. The * pin advances to the head this ceremony publishes, so a host that rolls the * log back immediately afterwards is caught by the next read. * * @param options {object} * @param options.idStore {WebvhIdStore} * @param options.updateKeys {ClientWebvhUpdateKeys} the current seeds * @param options.persistUpdateKeys {Function} awaited before every publish * that changes the log's authorized update keys * @param [options.expectedDid] {string} the DID the published log must * resolve to * @returns {Promise<{ did: string }>} */ export declare function rotateWebvhUpdateKey(options: { idStore: WebvhIdStore; updateKeys: ClientWebvhUpdateKeys; persistUpdateKeys: (next: ClientWebvhUpdateKeys) => Promise; expectedDid?: string; }): Promise<{ did: string; }>; /** * Asserts the carry-over commitment convention holds for every currently * authorized update key -- the precondition for any entry that re-states * `updateKeys` (the resolver checks the re-stated set against the previous * entry's `nextKeyHashes`). A log minted before the convention cannot take a * non-rotating entry and must be re-provisioned. * * @param options {object} * @param options.published {PublishedWebvhLog} * @returns {Promise} */ export declare function assertCarryOverCommitments({ published }: { published: PublishedWebvhLog; }): Promise; /** * The public halves of a client being enrolled: its published key set (the * Ed25519 signing key and X25519 key-agreement twin that become document * verification methods) plus its update-key pair -- the active key that joins * `updateKeys` and the staged key whose hash is committed so the new client * can later self-rotate. All four are `publicKeyMultibase` strings; the seeds * behind them never leave the client being enrolled. */ export interface WebvhEnrollmentKeys extends WebvhClientKeys { updateKeyMultibase: string; stagedUpdateKeyMultibase: string; } //# sourceMappingURL=didWebvh.d.ts.map