import { EdvClientCore } from '@interop/edv-client'; import type { IKeyAgreementKey, IKeyResolver } from '@interop/data-integrity-core'; import type { CodecIndexing, CodecRequestContext, CodecWrite, EncryptionProvider, ResourceCodec, ResponseLike } from '../codec.js'; import type { WritePrecondition } from '../internal/conditional.js'; import { WasTransport } from './WasTransport.js'; import type { BlindingKey } from './hmacKey.js'; import type { CollectionEncryption, Json, ResourceData, ResourceMetadataCustom, ResourceMetadataCustomInput } from '../types.js'; /** * Builds the `WasTransport` a codec's chunked-stream paths drive, over the * signed requester core of one request. Injected into {@link EdvCodec} by the * build that knows where the Collection lives (a Space on a server); a codec * built without one has no server behind it and refuses the chunked path. * * @param options {object} * @param options.context {CodecRequestContext} the signed-request context * @param [options.documentHeaders] {Record} extra headers * for document writes (the `Key-Epoch` stamp the codec seam applies) * @returns {WasTransport} */ export type CodecTransportFactory = (options: { context: CodecRequestContext; documentHeaders?: Record; }) => WasTransport; /** * Builds the transport factory for a Collection reachable over WAS: the * codec's route to its own document and chunk resources on the server. * * @param options {object} * @param options.spaceId {string} the Space holding the Collection * @param options.collectionId {string} the Collection * @param options.contentType {string} stored envelope content type * @returns {CodecTransportFactory} */ export declare function wasTransportFactory({ spaceId, collectionId, contentType }: { spaceId: string; collectionId: string; contentType: string; }): CodecTransportFactory; /** * A {@link ResourceCodec} that encrypts on write and decrypts on read using an * `EdvClientCore`'s public `documentCipher`. One instance is bound per encrypted * collection handle. */ export declare class EdvCodec implements ResourceCodec { #private; readonly conditionalWrites = true; /** * @param options {object} * @param options.edv {EdvClientCore} holds the cipher + key resolver * @param options.keyAgreementKey {IKeyAgreementKey} the key writes encrypt * under: the reconstructed `currentEpoch` key pair. * @param options.readKeys {IKeyAgreementKey[]} the candidate keys a read * may decrypt with: one per epoch this reader can unwrap. A read selects * the one whose id matches the stored envelope's recipient, so a resource * written under an older epoch still decrypts. * @param options.writeEpoch {string} the key-epoch id to stamp on writes * (the `currentEpoch`), surfaced as {@link EncodedWrite.epoch} and bound * into every envelope's `was.epoch`, which the decode side checks against * the decrypting key's epoch unconditionally. * @param options.contentType {string} stored envelope content type * @param options.maxBlobBytes {number} the size above which a binary write * is routed to the chunked-stream path instead of one document * @param [options.chunkSize] {number} the size of each encrypted chunk a * routed write emits (defaults to the EDV core's 1 MiB). One chunk is one * upload, so it must stay under the backend's `maxUploadBytes`; that * constraint is not advertised through the feature probe, so it is the * caller's to respect (see `createEdvEncryption`) * @param options.idDerivation {string} how `add()` mints a document * id: `'random'` (classic `generateId()`) or `'content'` (derived from the * JWE ciphertext, content-addressed) * @param [options.version] {number} the EDV-over-WAS scheme version to bind * into each envelope's `was.v` (defaults to {@link EDV_SCHEME_VERSION}) * @param [options.transportFactory] {CodecTransportFactory} builds the * transport the chunked-stream path drives; omitted by a build with no * server behind it, which then refuses that path * @param options.collectionId {string} the Collection this codec reads and * writes: bound into the Collection metadata envelope's `was.collection` * (and checked on read), and it labels decrypt-routing errors * @param options.epochIds {string[]} the id of every epoch the descriptor * lists (held by this reader or not); decrypt routing checks it to tell a * not-a-recipient envelope apart from a stale-descriptor one * @param [options.hmac] {BlindingKey} the collection's blinded-index key, * where it declares one */ constructor({ edv, keyAgreementKey, readKeys, writeEpoch, contentType, maxBlobBytes, chunkSize, idDerivation, version, transportFactory, collectionId, epochIds, hmac }: { edv: EdvClientCore; keyAgreementKey: IKeyAgreementKey; readKeys: IKeyAgreementKey[]; writeEpoch: string; contentType: string; maxBlobBytes: number; chunkSize?: number; idDerivation: 'random' | 'content'; version?: number; transportFactory?: CodecTransportFactory; collectionId: string; epochIds: string[]; hmac?: BlindingKey | null; }); /** * The collection's blinded-index key, or `null` where it declares none. * * @returns {BlindingKey | null} */ get blindingKey(): BlindingKey | null; /** * @inheritdoc */ get indexing(): CodecIndexing | undefined; /** * @inheritdoc */ encode({ id, data, contentType, current, precondition }: { id?: string; data: ResourceData; contentType?: string; current?: ResponseLike | null; precondition?: WritePrecondition; }): Promise; /** * @inheritdoc */ decode(response: ResponseLike, expectedId?: string, context?: CodecRequestContext): Promise; /** * @inheritdoc * * Encrypts the user-writable `custom` into an EDV Document envelope * (`{ jwe, ... }`) with the same `documentCipher.encrypt` used for content -- * `custom` becomes the document `content`. The envelope's own `sequence` is * inert (metadata concurrency is the server's plaintext `metaVersion`, not the * envelope), so each write re-encrypts fresh with no `update`. */ encodeMeta({ custom, id: resourceId }: { custom: ResourceMetadataCustomInput; id?: string; }): Promise<{ custom: object; epoch: string; }>; /** * @inheritdoc * * Decrypts the stored `custom` envelope back to plaintext `{ name, tags }`. An * absent `custom` (no metadata written yet, or cleared) decodes to `{}`; a * present value must be an EDV envelope (else {@link EncryptionError}, the * `_assertEnvelope` guard), so a foreign plaintext `custom` fails closed. * * An omitted `expectedId` means the Collection-level metadata slot (a * Resource metadata read always passes its resource id), so an envelope bound * to a resource is refused there as a server-side swap, and one that does not * bind this Collection's own id is refused as an envelope of some other slot. */ decodeMeta({ custom }: { custom?: unknown; }, expectedId?: string): Promise; } /** * The per-collection key material an EDV codec is built from. */ export interface EdvKeys { keyAgreementKey: IKeyAgreementKey; keyResolver: IKeyResolver; /** * The collection's blinded-index key, for a keystore that custodies the HMAC * key directly rather than reading it off the descriptor. An explicitly * supplied key wins over unwrapping the descriptor's `hmac` member; omit it * to let the descriptor decide (and to get `null` when the collection * declares no blinded index at all). */ hmac?: BlindingKey; } /** * Builds an {@link EncryptionProvider} for the `edv` scheme: a pure **keystore** * that turns a collection's keys into an {@link EdvCodec}. Pass the result as * `WasClient`'s `encryption` option. * * It does **not** decide which collections are encrypted -- that policy is the * Collection's `encryption` descriptor (or a per-handle override). Core calls * `codecFor` only for a collection already known to be encrypted; this provider * then supplies the keys: the override-supplied `keys` when present, else * `resolveKeys({ spaceId, collectionId })`. `resolveKeys` returning `null` means * "I hold no keys for this collection", so core fails closed (it does **not** * mean plaintext -- the descriptor/override already decided that). A non-`edv` * scheme yields `null` (this provider does not handle it). * * @param options {object} * @param options.resolveKeys {function} the keystore: returns the collection's * `{ keyAgreementKey, keyResolver }`, or `null` if this client holds no keys * for it (fail-closed -- not a plaintext signal) * @param [options.contentType] {string} stored envelope content type; * defaults to `application/json`. Pass `JOSE_CONTENT_TYPE` * (`application/jose+json`) against a server that registers an * `application/*+json` parser. * @param [options.maxBlobBytes] {number} the size in raw bytes above which a * binary `add()` is routed to the chunked-stream path instead of one document * (default 512 KiB, sized so a single-document envelope stays under a * server's ~1 MiB JSON body cap; raise it against a server with a larger * limit). A routing threshold, not a hard cap. * @param [options.chunkSize] {number} the size of each encrypted chunk a * routed write emits, in bytes (default 1 MiB). Each chunk is one upload, so * it must stay under the backend's `maxUploadBytes` constraint (the * encrypted chunk is somewhat larger than `chunkSize`, so leave headroom). * This is not checked client-side: the shared backend probe reads the * descriptor's affordance tokens, not its `constraints`, so a chunk over the * limit is rejected by the server with a `PayloadTooLargeError` (413) and * the failed write's document stub is then cleaned up. * @param [options.idDerivation] {string} how `add()` mints a document id. * `'random'` (default) is the classic mutable-document model: a random * `generateId()` id, updated in place via `sequence`. `'content'` derives the * id from the encrypted envelope's JWE ciphertext * (`EdvDocumentCipher.deriveId`), making documents content-addressed and * therefore immutable (an "update" is delete-old + add-new) -- the model a * replicating store wants, since the id is stable across replicas with no * mapping table. Both formats pass the same EDV id check; the explicit-id * `put(id, ...)` path is unaffected either way. * @returns {EncryptionProvider} */ export declare function createEdvEncryption({ resolveKeys, contentType, maxBlobBytes, chunkSize, idDerivation }: { resolveKeys: (ref: { spaceId: string; collectionId: string; }) => Promise; contentType?: string; maxBlobBytes?: number; chunkSize?: number; idDerivation?: 'random' | 'content'; }): EncryptionProvider; /** * Builds the {@link EdvCodec} for one encrypted collection from a reader's * keys and the collection's encryption descriptor. The whole of the codec * build that follows key resolution: the fail-closed descriptor guard, the * reader's per-epoch keys, the collection's blinding key, and the EDV core the * codec drives. Shared by the keystore provider (`createEdvEncryption`'s * `codecFor`, which resolves the keys first) and by the local-replica cipher, * which holds its keys already. * * @param options {object} * @param options.collectionId {string} the collection's WAS id * @param [options.label] {string} how the collection is named in errors; * defaults to its id alone, which is all a build with no Space knows * @param [options.transportFactory] {CodecTransportFactory} builds the * transport the chunked-stream path drives; omitted by the local-replica * build, which has no server to address * @param [options.encryption] {CollectionEncryption} the collection's * encryption descriptor; must carry the key-epoch roster * @param options.keys {EdvKeys} the reader's key material * @param options.idDerivation {'random' | 'content'} how `add()` mints ids * @param [options.contentType] {string} stored envelope content type * @param [options.maxBlobBytes] {number} the single-document threshold * @param [options.chunkSize] {number} the size of each encrypted chunk * @returns {Promise} */ export declare function buildEdvCodec({ collectionId, label, transportFactory, encryption, keys, idDerivation, contentType, maxBlobBytes, chunkSize }: { collectionId: string; label?: string; transportFactory?: CodecTransportFactory; encryption?: CollectionEncryption; keys: EdvKeys; idDerivation: 'random' | 'content'; contentType?: string; maxBlobBytes?: number; chunkSize?: number; }): Promise; /** * Builds a write-only {@link EdvCodec} for one encrypted collection from its * descriptor alone -- no key-agreement secret anywhere. Encryption in this * scheme needs none: a write seals a fresh content-encryption key to the write * epoch's PUBLIC key, and the epoch id IS that key's did:key, so the write * recipient is reconstructed from the descriptor's `currentEpoch` and resolved * through the standard did:key resolver. The codec's write key is a * public-only stand-in (the id the recipient template and resolver work from; * its `deriveSecret` refuses with {@link EncryptOnlyCipherError}) and it holds * no read keys, so it encrypts everything and decrypts nothing. * * Applies the same fail-closed guards as `codecFor`: a future-scheme * descriptor and a descriptor without epochs are refused. The write epoch is * the descriptor's `currentEpoch`; a descriptor that omits it seals to the * last listed epoch (the roster is append-only, so that is the newest). A * `currentEpoch` the roster does not list violates the descriptor invariant * and is refused fail-closed -- silently re-routing it could seal new * plaintext to a rotated-out epoch whose removed recipients still hold keys. * * @param options {object} * @param options.collectionId {string} labels errors; and on a slot-bound * write, the id bound into the envelope * @param options.idDerivation {'content' | 'random'} how ids are minted * @param options.encryption {CollectionEncryption} the collection's * descriptor; must carry the key-epoch roster * @returns {Promise} */ export declare function encryptOnlyEdvCodec({ collectionId, idDerivation, encryption }: { collectionId: string; idDerivation: 'content' | 'random'; encryption: CollectionEncryption; }): Promise; //# sourceMappingURL=EdvCodec.d.ts.map