| /** | |
| * Cryptographic service for encrypted event-log replication. | |
| * | |
| * `EventLogEncryption` turns local journal entries into encrypted remote | |
| * payloads and decrypts encrypted changes received from a server. It also | |
| * hashes byte data and creates event-log identities, so remote replication can | |
| * use storage or transport that should not see plaintext event data. | |
| * | |
| * @since 4.0.0 | |
| */ | |
| import * as Context from "../../Context.ts" | |
| import * as Effect from "../../Effect.ts" | |
| import * as Layer from "../../Layer.ts" | |
| import * as Redacted from "../../Redacted.ts" | |
| import * as Schema from "../../Schema.ts" | |
| import * as Transferable from "../workers/Transferable.ts" | |
| import { Entry, EntryId, RemoteEntry } from "./EventJournal.ts" | |
| import type { Identity } from "./EventLog.ts" | |
| import { makeGetIdentityRootSecretMaterial } from "./internal/identityRootSecretDerivation.ts" | |
| /** | |
| * Schema for an encrypted journal entry paired with the id of the original | |
| * entry. | |
| * | |
| * @category models | |
| * @since 4.0.0 | |
| */ | |
| export const EncryptedEntry = Schema.Struct({ | |
| entryId: EntryId, | |
| encryptedEntry: Transferable.Uint8Array | |
| }) | |
| /** | |
| * Type of an encrypted remote entry, including its remote sequence number, | |
| * initialization vector, entry id, and encrypted entry bytes. | |
| * | |
| * @category models | |
| * @since 4.0.0 | |
| */ | |
| export interface EncryptedRemoteEntry extends Schema.Schema.Type<typeof EncryptedRemoteEntry> {} | |
| /** | |
| * Schema for encrypted entries exchanged with a remote event-log server. | |
| * | |
| * @category models | |
| * @since 4.0.0 | |
| */ | |
| export const EncryptedRemoteEntry = Schema.Struct({ | |
| sequence: Schema.Number, | |
| iv: Transferable.Uint8Array, | |
| entryId: EntryId, | |
| encryptedEntry: Transferable.Uint8Array | |
| }) | |
| const toArrayBuffer = (data: Uint8Array): ArrayBuffer => { | |
| const buffer = new ArrayBuffer(data.byteLength) | |
| new Uint8Array(buffer).set(data) | |
| return buffer | |
| } | |
| const toBufferSource = (data: Uint8Array): ArrayBufferView<ArrayBuffer> => new Uint8Array(toArrayBuffer(data)) | |
| /** | |
| * Service that provides identity generation, entry | |
| * encryption and decryption, and SHA-256 hashing for event-log replication. | |
| * | |
| * **When to use** | |
| * | |
| * Use to provide cryptographic operations required by encrypted event-log | |
| * replication. | |
| * | |
| * @category services | |
| * @since 4.0.0 | |
| */ | |
| export class EventLogEncryption extends Context.Service<EventLogEncryption, { | |
| readonly encrypt: ( | |
| identity: Identity["Service"], | |
| entries: ReadonlyArray<Entry> | |
| ) => Effect.Effect<{ | |
| readonly iv: Uint8Array<ArrayBuffer> | |
| readonly encryptedEntries: ReadonlyArray<Uint8Array<ArrayBuffer>> | |
| }> | |
| readonly decrypt: ( | |
| identity: Identity["Service"], | |
| entries: ReadonlyArray<EncryptedRemoteEntry> | |
| ) => Effect.Effect<Array<RemoteEntry>> | |
| readonly sha256String: (data: Uint8Array) => Effect.Effect<string> | |
| readonly sha256: (data: Uint8Array) => Effect.Effect<Uint8Array> | |
| readonly generateIdentity: Effect.Effect<Identity["Service"]> | |
| }>()("effect/eventlog/EventLogEncryption") {} | |
| /** | |
| * Creates an `EventLogEncryption` service backed by the Web Crypto `SubtleCrypto` | |
| * APIs from the supplied `Crypto` implementation. | |
| * | |
| * @category encryption | |
| * @since 4.0.0 | |
| */ | |
| export const makeEncryptionSubtle = (crypto: Crypto): Effect.Effect<EventLogEncryption["Service"]> => | |
| Effect.sync(() => { | |
| const getIdentityRootSecretMaterial = makeGetIdentityRootSecretMaterial(crypto) | |
| return EventLogEncryption.of({ | |
| encrypt: Effect.fnUntraced(function*(identity, entries) { | |
| const data = yield* Effect.orDie(Entry.encodeArray(entries)) | |
| const key = (yield* getIdentityRootSecretMaterial(identity)).encryptionKey | |
| const iv = crypto.getRandomValues(new Uint8Array(12)) | |
| const encryptedEntries = yield* Effect.promise(() => | |
| Promise.all( | |
| data.map((entry) => | |
| crypto.subtle.encrypt( | |
| { name: "AES-GCM", iv: toBufferSource(iv), tagLength: 128 }, | |
| key, | |
| toBufferSource(entry) | |
| ) | |
| ) | |
| ) | |
| ) | |
| return { | |
| iv, | |
| encryptedEntries: encryptedEntries.map((entry) => new Uint8Array(entry)) | |
| } | |
| }), | |
| decrypt: Effect.fnUntraced(function*(identity, entries) { | |
| const key = (yield* getIdentityRootSecretMaterial(identity)).encryptionKey | |
| const decryptedData = (yield* Effect.promise(() => | |
| Promise.all(entries.map((data) => | |
| crypto.subtle.decrypt( | |
| { name: "AES-GCM", iv: toBufferSource(data.iv), tagLength: 128 }, | |
| key, | |
| toBufferSource(data.encryptedEntry) | |
| ) | |
| )) | |
| )).map((buffer) => new Uint8Array(buffer)) | |
| const decoded = yield* Effect.orDie(Entry.decodeArray(decryptedData)) | |
| return decoded.map((entry, index) => new RemoteEntry({ remoteSequence: entries[index].sequence, entry })) | |
| }), | |
| sha256: (data) => | |
| Effect.promise(() => crypto.subtle.digest("SHA-256", toArrayBuffer(data))).pipe( | |
| Effect.map((hash) => new Uint8Array(hash)) | |
| ), | |
| sha256String: (data) => | |
| Effect.map( | |
| Effect.promise(() => crypto.subtle.digest("SHA-256", toArrayBuffer(data))), | |
| (hash) => { | |
| const hashArray = Array.from(new Uint8Array(hash)) | |
| const hashHex = hashArray | |
| .map((bytes) => bytes.toString(16).padStart(2, "0")) | |
| .join("") | |
| return hashHex | |
| } | |
| ), | |
| generateIdentity: Effect.sync(() => ({ | |
| publicKey: crypto.randomUUID(), | |
| privateKey: Redacted.make(crypto.getRandomValues(new Uint8Array(32))) | |
| })) | |
| }) | |
| }) | |
| /** | |
| * Provides `EventLogEncryption` using `globalThis.crypto`. | |
| * | |
| * @category encryption | |
| * @since 4.0.0 | |
| */ | |
| export const layerSubtle: Layer.Layer<EventLogEncryption> = Layer.effect( | |
| EventLogEncryption, | |
| makeEncryptionSubtle(globalThis.crypto) | |
| ) | |
Xet Storage Details
- Size:
- 5.88 kB
- Xet hash:
- 776caba42231723f8795058af15d2d0199f5a8bd5c7c3827e6a2e73ea0192c12
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.