import type { IKeyAgreementKey, IKeyResolver, IZcap } from '@interop/data-integrity-core'; import type { CollectionEncryption } from '@interop/was-client'; import type { AccountPointer, SignedRecord } from '../keyring/recordEnvelope.js'; import type { RecordSigner } from '../keyring/record.js'; /** * The byte length of a ladder seed: 32 random bytes, minted at bind time and * carried only inside the unlock record's sealed ladder member. Declared * here because the record format owns its member sizes; the ladder module's * derivations import it from this codec. */ export declare const LADDER_SEED_BYTES = 32; /** * An unlock record's account binding is absent, malformed, or does not verify * under the credential's binding MAC key. Its own class, distinct from a * proof or decrypt failure: this is the refusal that says the record's * account core (and ladder seed, where one rides) was not written by a holder * of this credential -- a forged record redirecting login at another account, * or a record from before the account moved hosts (either way the credential * must be rebound). */ export declare class UnlockBindingError extends Error { constructor(message: string); } /** * A self-contained sealed record member: its own one-epoch descriptor plus * the envelope sealed under it. Self-containment is what lets a member travel * VERBATIM through a re-mint that re-seals its siblings -- each member * decrypts against its own descriptor, not the frame's. */ export interface SealedRecordMember { encryption: CollectionEncryption; wrapped: unknown; } /** * The unwrapped contents of an unlock record: the credential-authenticated * account core (controller + pointer, plus the ladder seed where the * credential is a standing method), the bridge delegation, the optional * annex Space delegation (`delegatedClients` -- a standing credential's * pre-minted GET+PUT over the auxiliary annex Space), * the optional bind email, and the bind timestamp. `pointer` is required -- * the record exists only on WAS deployments. */ export interface UnlockRecordContents { controller: string; email?: string; pointer: AccountPointer; delegation: IZcap; delegatedClients?: IZcap; ladderSeed?: Uint8Array; createdAt: string; } /** * A stored unlock record: the shared signed frame plus the record-kind * members the frame proof also covers. */ export interface SignedUnlockRecord extends SignedRecord { binding: string; bridge: SealedRecordMember; delegatedClients?: SealedRecordMember; ladder?: SealedRecordMember; } /** * Where an unlock record's proof stands after the unwrap: `'verified'` when * the credential-derived unlock key signed it (checked before decryption), or * a pending marker naming the signer the caller must still check against the * account's verified did:webvh document -- the re-mint case, where an * enrolled client signed on the credential's behalf. The pending case defers * the shell's authenticity only, never the account identity: the binding is * verified either way. */ export type UnlockRecordProofState = 'verified' | { pending: { verificationMethod: string; keyMultibase: string; }; }; /** * Computes the account-binding tag: HMAC-SHA-256 over the core values under * the credential-derived binding MAC key, base64url (no pad). Bind time calls * it to stamp the record; login recomputes it to verify. * * @param options {object} * @param options.bindingMacKey {Uint8Array} the credential-derived MAC key * @param options.controller {string} the account controller * @param options.pointer {AccountPointer} the account pointer * @param [options.ladderSeed] {Uint8Array} the ladder seed, for a standing * credential * @returns {string} */ export declare function computeUnlockBinding({ bindingMacKey, controller, pointer, ladderSeed }: { bindingMacKey: Uint8Array; controller: string; pointer: AccountPointer; ladderSeed?: Uint8Array; }): string; /** * Reads the `binding` frame member off a stored unlock record without * decrypting anything. The re-mint path uses it to confirm the record is * re-mintable before touching it. Refuses a record with no binding: such a * record predates the credential-authenticated core and cannot be re-minted * -- its credential must be rebound. * * @param options {object} * @param options.record {unknown} the stored record envelope * @returns {string} */ export declare function unlockRecordBinding({ record }: { record: unknown; }): string; /** * The key-agreement keys a stored unlock record is currently sealed to: the * `kid` fragments of its frame descriptor's current-epoch recipients (a * record's descriptor carries one epoch, minted by * {@link mintRecordEncryption}, so the current epoch is the whole roster). * Public halves only -- nothing here is secret, and reading them decrypts * nothing. A descriptor with no epochs, or whose `currentEpoch` names no * epoch it lists, is refused rather than read as an empty recipient set. * * @param options {object} * @param options.record {unknown} the stored record envelope * @returns {string[]} the recipients' key multibases */ export declare function recordSealedRecipientKeys({ record }: { record: unknown; }): string[]; /** * Whether a stored unlock record is sealed to the named credential's unlock * key-agreement key. The detector behind the pending-shaped registry entry: * a passphrase change torn before its retirement landed leaves an entry whose * `unlockSpaceId` and `manageCapability` are the NEW credential's while its * identity members are the OLD credential's, so the record fetched at that * Space is sealed to a key the entry does not name. Compared on the key * multibase, since a `kid` and a recorded key id may be spelled differently. * Throws on a record whose frame or descriptor is unusable, so a caller * never reads a broken record as a pending-shaped one. * * @param options {object} * @param options.record {unknown} the stored record envelope * @param options.keyAgreementKeyMultibase {string} the credential's unlock * KAK public multibase, as the registry entry records it * @returns {boolean} */ export declare function unlockRecordSealedTo({ record, keyAgreementKeyMultibase }: { record: unknown; keyAgreementKeyMultibase: string; }): boolean; /** * Wraps an unlock record at bind time: the shell (controller, optional * email, pointer, timestamp) sealed to the credential's unlock KAK, the * bridge delegation and the optional ladder seed sealed as their own * members, the credential-authenticated binding computed over the core, and * the whole frame signed -- by the credential-derived unlock key at bind * time, or by an acting client's account key where a ceremony rebuilds a * record whole (the reader settles the mixed-signer policy either way). * * @param options {object} * @param options.controller {string} the account controller * @param [options.email] {string} the account email, when known * @param options.pointer {AccountPointer} the account pointer * @param options.delegation {IZcap} the PUT-on-`did.jsonl` delegation to * the credential-derived signing DID * @param [options.delegatedClients] {IZcap} the annex Space delegation * (GET+PUT over the auxiliary Space), for a standing * credential (a recovery code carries none) * @param [options.ladderSeed] {Uint8Array} the update-key ladder seed, for * a standing credential (a recovery code carries none) * @param options.keyAgreementKey {IKeyAgreementKey} the credential's unlock * KAK -- its public half is all the wrap uses * @param options.signer {RecordSigner} the signing key * @param options.bindingMacKey {Uint8Array} the credential-derived binding * MAC key * @param [options.createdAt] {string} the bind timestamp to stamp, as an * ISO string; defaults to now. Supplied by a caller that pins record * freshness. * @returns {Promise} */ export declare function wrapUnlockRecord({ controller, email, pointer, delegation, delegatedClients, ladderSeed, keyAgreementKey, signer, bindingMacKey, createdAt }: { controller: string; email?: string; pointer: AccountPointer; delegation: IZcap; delegatedClients?: IZcap; ladderSeed?: Uint8Array; keyAgreementKey: IKeyAgreementKey; signer: RecordSigner; bindingMacKey: Uint8Array; createdAt?: string; }): Promise; /** * Unwraps and validates an unlock record: the ordinary keyring-record frame * checks plus the required pointer, bridge delegation, and * credential-authenticated binding. A record without a bridge member is not * an unlock record in the standing layout (an ordinary keyring record found * under a credential's unlock Space would mean a torn bind) and is refused. * * The binding is verified before the contents are returned: the decrypted * core must carry the tag the credential-derived MAC key computes over it, or * the record is refused as forged ({@link UnlockBindingError}) -- the check * that closes the host-forgery redirect, since the host never holds the MAC * key. Nothing downstream may trust the pointer (or the ladder seed) before * this returns. * * Proof verification is mixed-signer and ordered deliberately, exactly as the * recovery record's was: a proof by the credential's own unlock key is * verified BEFORE decryption; a proof by any other key comes back as a * pending state naming the signer, for the caller to settle with * `verifyRecordProof` against the verified document of the account the * credential-authenticated pointer names. * * @param options {object} * @param options.record {unknown} the stored record envelope * @param options.keyAgreementKey {IKeyAgreementKey} the credential's unlock * KAK * @param options.keyResolver {IKeyResolver} * @param options.expectedKeyMultibase {string} the credential-derived * unlock signing key's multibase * @param options.bindingMacKey {Uint8Array} the credential-derived binding * MAC key the record's account binding must verify under * @returns {Promise<{ contents: UnlockRecordContents, * proofState: UnlockRecordProofState }>} */ export declare function unwrapUnlockRecord({ record, keyAgreementKey, keyResolver, expectedKeyMultibase, bindingMacKey }: { record: unknown; keyAgreementKey: IKeyAgreementKey; keyResolver: IKeyResolver; expectedKeyMultibase: string; bindingMacKey: Uint8Array; }): Promise<{ contents: UnlockRecordContents; proofState: UnlockRecordProofState; }>; /** * Re-mints an unlock record's pre-minted delegations: the revocation-cascade * path that replaces a rotted or expiring bridge (and, where the record * carries one, its annex Space sibling) while touching nothing else. The * shell, the ladder member, and the binding are carried VERBATIM (the re-mint * cannot decrypt any of them and does not need to -- each is self-contained * and the binding covers the core), the fresh delegations are sealed to the * credential's unlock KAK public half, and the frame is re-signed by the * acting client's account key -- the mixed-signer case the reader settles * against the verified document. * * The two members rot on one axis (same signer, same current-key-set rule, * same renewal window), so a re-mint pass reseals both atomically; a * `delegatedClients` member the caller supplies no fresh delegation for is * carried verbatim, sealed as it stands (self-contained, like the ladder) -- * the fallback for a pass that cannot rebuild the annex target, never * the intended steady state. * * A record with no binding cannot be re-minted ({@link UnlockBindingError}); * its credential must be rebound. * * @param options {object} * @param options.record {unknown} the standing stored record * @param options.delegation {IZcap} the freshly minted `did.jsonl` * delegation * @param [options.delegatedClients] {IZcap} the freshly minted * annex Space delegation; when absent, an existing `delegatedClients` * member travels verbatim * @param options.keyAgreementKey {IKeyAgreementKey} the credential's unlock * KAK, public half only * @param options.signer {RecordSigner} the acting client's account key * @returns {Promise} */ export declare function remintUnlockRecordDelegations({ record, delegation, delegatedClients, keyAgreementKey, signer }: { record: unknown; delegation: IZcap; delegatedClients?: IZcap; keyAgreementKey: IKeyAgreementKey; signer: RecordSigner; }): Promise; //# sourceMappingURL=unlockRecord.d.ts.map