@typeset-page/docshare-crypto
v0.1.0
Published
End-to-end encryption (AES-GCM 256 + wire formats) for the Doc Share pure-publish mode. Framework-free WebCrypto; the single source of truth shared by share-web, the Obsidian plugin and the minimal encrypted reader.
Maintainers
Readme
@typeset-page/docshare-crypto
End-to-end encryption for the Doc Share pure-publish (no-account) mode.
This is the single source of truth for the crypto. It used to be hand-mirrored
across share-web, the Obsidian plugin and the reader; now everyone imports this
one package. It is deliberately tiny, framework-free (WebCrypto only) and
auditable — it is the code that decides whether the server can ever see your
cleartext. It cannot.
Guarantees
- AES-GCM 256. The key is generated in the browser and never leaves it:
it lives only in the URL fragment (
…/p/<id>#<key>), which browsers never send to the server. The host stores nothing but ciphertext. - The title is encrypted too — the plaintext is a JSON
PlainPayload({ title, html }), so the DB row'stitleisnullfor encrypted pages. - Tamper-evident. GCM's auth tag makes
decryptPayloadthrow on any modified ciphertext or a wrong key.
Wire formats (stable — the reader parses these)
| Piece | Format |
|---|---|
| URL fragment | base64url(rawKey) → …/p/<id>#<frag> |
| stored payload (pages.html when enc='v1') | base64url(iv) + ':' + base64url(ciphertext‖gcmTag) |
| plaintext (pre-encryption) | JSON of PlainPayload |
API
import {
generateKey, exportKeyToFragment, importKeyFromFragment, keyFragmentFromHash,
encryptPayload, decryptPayload, type PlainPayload,
} from '@typeset-page/docshare-crypto';Consuming it (Phase A: in-monorepo)
Consumers import the TypeScript source directly through a file: dependency,
so there is no build-ordering step and only one copy exists:
// share-web / obsidian-docshare package.json
"dependencies": { "@typeset-page/docshare-crypto": "file:../packages/docshare-crypto" }Both bundlers (Vite for share-web, esbuild for the plugin and the encrypted
reader) compile the .ts transparently. main/types point at the source too
because obsidian-docshare uses classic moduleResolution: node, which ignores
the exports field.
Publishing
Source-primary by design. The published tarball ships src/crypto.ts and
main/types/exports point at it. Every real consumer is a bundler
(Vite/esbuild) that compiles the TS on the fly, and this is an auditability
package — the tarball is the readable code, world-readable on npm. npm run
build still emits ./dist for typecheck/CI and any future plain-Node consumer.
Develop
npm install
npm test # vitest, Node WebCrypto — round-trip, tamper, wrong-key, malformed
npm run build # tsc → dist/