@interncom/diplomatic 0.11.4 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/cli/index.mjs +2 -2
  2. package/dist/cli/shared/binary.d.ts +4 -0
  3. package/dist/cli/shared/codecs/bundleHost.d.ts +11 -0
  4. package/dist/cli/shared/codecs/identityBundle.d.ts +25 -0
  5. package/dist/cli/shared/codecs/pairPackage.d.ts +15 -0
  6. package/dist/cli/shared/crypto/enclave.d.ts +113 -2
  7. package/dist/cli/shared/crypto/entropy.d.ts +11 -0
  8. package/dist/cli/shared/seed.d.ts +25 -0
  9. package/dist/cli/shared/types.d.ts +4 -6
  10. package/dist/cli/shared/webauthn/common.d.ts +38 -0
  11. package/dist/cli/shared/webauthn/largeBlob.d.ts +19 -0
  12. package/dist/cli/shared/webauthn/prf.d.ts +33 -0
  13. package/dist/cli/shared/worker/embeddedWorkerSource.d.ts +1 -0
  14. package/dist/cli/shared/worker/spawn.d.ts +5 -0
  15. package/dist/cli/src/index.d.ts +10 -8
  16. package/dist/web/client.d.ts +3 -2
  17. package/dist/web/identity/pairPackage.d.ts +31 -0
  18. package/dist/web/index.d.ts +18 -6
  19. package/dist/web/index.mjs +1 -1
  20. package/dist/web/openClient.d.ts +14 -109
  21. package/dist/web/passkey/largeBlob.d.ts +17 -0
  22. package/dist/web/passkey/prf-store.d.ts +62 -0
  23. package/dist/web/passkey/seed.d.ts +13 -99
  24. package/dist/web/passkey/webauthn.d.ts +1 -0
  25. package/dist/web/react/useClient.d.ts +13 -20
  26. package/dist/web/shared/binary.d.ts +4 -0
  27. package/dist/web/shared/codecs/bundleHost.d.ts +11 -0
  28. package/dist/web/shared/codecs/identityBundle.d.ts +25 -0
  29. package/dist/web/shared/codecs/pairPackage.d.ts +15 -0
  30. package/dist/web/shared/codecs/pairPackageEnvelope.d.ts +18 -0
  31. package/dist/web/shared/crypto/enclave.d.ts +113 -2
  32. package/dist/web/shared/crypto/entropy.d.ts +11 -0
  33. package/dist/web/shared/seed.d.ts +25 -0
  34. package/dist/web/shared/types.d.ts +4 -6
  35. package/dist/web/shared/webauthn/common.d.ts +38 -0
  36. package/dist/web/shared/webauthn/largeBlob.d.ts +19 -0
  37. package/dist/web/shared/webauthn/prf.d.ts +33 -0
  38. package/dist/web/shared/worker/embeddedWorkerSource.d.ts +1 -0
  39. package/dist/web/shared/worker/spawn.d.ts +5 -0
  40. package/dist/web/stores/idb/seed.d.ts +24 -6
  41. package/dist/web/stores/memory/seed.d.ts +3 -4
  42. package/dist/web/types.d.ts +13 -7
  43. package/dist/web/worker/client.d.ts +26 -48
  44. package/dist/web/worker.mjs +1 -1
  45. package/package.json +1 -5
@@ -1,103 +1,10 @@
1
1
  import { IClock } from "./shared/clock";
2
2
  import type { IStateManager } from "./shared/types";
3
3
  import type { IClient, IStore } from "./types";
4
- /**
5
- * How to construct the Worker you pass to {@link openDiplomaticClient} /
6
- * {@link WorkerClient.connect} / `useClient`.
7
- *
8
- * The library accepts only a live `Worker` instance. Instantiation is bundler-
9
- * and hosting-specific — the app owns that step so DIPLOMATIC never guesses a
10
- * script URL that would 404 under a different tool.
11
- *
12
- * The worker entry is the package export `@interncom/diplomatic/worker`
13
- * (built as `worker.mjs` in the published package). It must run as a
14
- * **module** worker (`type: "module"`).
15
- *
16
- * ## Handshake (race-safe)
17
- *
18
- * The worker posts an unsolicited `{ kind: "ready" }` after init. Apps often
19
- * construct the Worker at module load (or before `openDiplomaticClient` finishes
20
- * opening IndexedDB), so that event can fire before the library sets
21
- * `onmessage`. **That is fine:** connect also probes with a request/response
22
- * `ping`. The worker holds commands until init completes, so the probe succeeds
23
- * even if `ready` was dropped. Do **not** reimplement message buffering in the
24
- * app unless you need it for other reasons.
25
- *
26
- * ## Vite (recommended for SPA templates)
27
- *
28
- * ```ts
29
- * import DiplomaticWorker from "@interncom/diplomatic/worker?worker";
30
- * const worker = new DiplomaticWorker();
31
- * await openDiplomaticClient({ state, worker });
32
- * ```
33
- *
34
- * `?worker` makes Vite emit a real worker asset and a constructor. Create the
35
- * instance once (module scope or `useMemo`/`useRef`) — not on every render.
36
- * Early construction is supported; the handshake recovers if `ready` is missed.
37
- *
38
- * ## webpack / Rollup / esbuild / Parcel (`new URL` + import.meta.url)
39
- *
40
- * Point at the package worker file so the bundler copies it next to the app:
41
- *
42
- * ```ts
43
- * const worker = new Worker(
44
- * new URL("@interncom/diplomatic/worker", import.meta.url),
45
- * { type: "module" },
46
- * );
47
- * ```
48
- *
49
- * If the package path does not resolve, import the built file explicitly:
50
- *
51
- * ```ts
52
- * const worker = new Worker(
53
- * new URL(
54
- * "../node_modules/@interncom/diplomatic/dist/worker.mjs",
55
- * import.meta.url,
56
- * ),
57
- * { type: "module" },
58
- * );
59
- * ```
60
- *
61
- * (Exact relative path depends on your app layout; prefer the package export
62
- * when your bundler supports it.)
63
- *
64
- * ## Plain HTML / CDN / static hosting (no bundler)
65
- *
66
- * Host `worker.mjs` (from the package `dist/`) as a same-origin static asset,
67
- * then:
68
- *
69
- * ```html
70
- * <script type="module">
71
- * import { openDiplomaticClient } from "https://cdn.example/diplomatic.js";
72
- * const worker = new Worker("/path/to/worker.mjs", { type: "module" });
73
- * const { client } = await openDiplomaticClient({ state, worker });
74
- * </script>
75
- * ```
76
- *
77
- * The worker script must be **same-origin** (or CORS-enabled for classic
78
- * workers; module workers generally need same-origin). Do not invent a path
79
- * into `node_modules` from the browser — copy or serve the built file.
80
- *
81
- * ## React (`useClient`)
82
- *
83
- * Same rules: build the Worker once, pass the instance:
84
- *
85
- * ```ts
86
- * import DiplomaticWorker from "@interncom/diplomatic/worker?worker";
87
- * const syncWorker = new DiplomaticWorker(); // module scope
88
- * useClient({ seed, host, worker: syncWorker });
89
- * ```
90
- *
91
- * ## Lifecycle
92
- *
93
- * Workers die with the page (tab close / full navigation). Mid-session death
94
- * is rare; if you recreate a Worker yourself, open a new client against it.
95
- * `dispose` / `WorkerClient.terminate` stops the worker DIPLOMATIC is using.
96
- */
97
4
  type OpenDiplomaticClientBase = {
98
5
  state: IStateManager;
99
6
  clock?: IClock;
100
- /** Max wait for worker ready when using a worker (default 15s). */
7
+ /** Max wait for setSeed Enclave.spawnSyncWorker handshake (default 15s). */
101
8
  readyTimeoutMs?: number;
102
9
  /**
103
10
  * Debounce local write → worker sync (default `defaultSyncDebounceMs`).
@@ -106,29 +13,29 @@ type OpenDiplomaticClientBase = {
106
13
  syncDebounceMs?: number;
107
14
  };
108
15
  /**
109
- * Worker path: protocol sync off the main thread. Main and worker both use
110
- * IndexedDB (shared durable state). A custom `store` is a type error and a
111
- * runtime error — the worker always opens IDB, so a MemoryStore (etc.) on
112
- * main would silently diverge.
16
+ * Worker path: protocol sync off the main thread after setSeed. Main and worker
17
+ * both use IndexedDB. Custom `store` is forbidden (worker always opens IDB).
18
+ *
19
+ * No worker is spawned at open. Only {@link Enclave.spawnSyncWorker} (from
20
+ * setSeed) creates a worker and injects seed.
21
+ *
22
+ * CSP: allow `worker-src blob:` (or `script-src blob:`) if you use a strict CSP.
113
23
  */
114
24
  export type OpenDiplomaticClientWorkerOptions = OpenDiplomaticClientBase & {
115
- /**
116
- * App-constructed sync Worker (see module docs above for how to create it).
117
- * Misconfiguration or ready timeout throws — no silent main-thread fallback.
118
- */
119
- worker: Worker;
25
+ /** Request a library-managed sync worker. */
26
+ worker: true;
120
27
  store?: never;
121
28
  };
122
29
  /**
123
30
  * Main-thread path: SyncClient on the page. Optional custom store; default is
124
- * IndexedDB. No automatic memory fallback when IndexedDB is missing.
31
+ * IndexedDB.
125
32
  */
126
33
  export type OpenDiplomaticClientMainOptions = OpenDiplomaticClientBase & {
127
- worker?: undefined;
34
+ worker?: undefined | false;
128
35
  /**
129
36
  * Protocol message store. Default: IndexedDB (`openIDBStore`).
130
37
  * Pass explicitly for non-IDB backends (e.g. `new MemoryStore(crypto)`).
131
- * Incompatible with `worker` (see {@link OpenDiplomaticClientWorkerOptions}).
38
+ * Incompatible with `worker: true`.
132
39
  */
133
40
  store?: IStore<URL>;
134
41
  };
@@ -143,10 +50,8 @@ export type OpenedDiplomaticClient = {
143
50
  /**
144
51
  * Open a browser client.
145
52
  *
146
- * - **Worker path:** pass `worker` (already constructed). Always IndexedDB.
53
+ * - **Worker path:** `worker: true` façade ready; Worker appears on setSeed.
147
54
  * - **Main path:** omit `worker` → SyncClient on the page; optional `store`.
148
- *
149
- * See {@link OpenDiplomaticClientOptions} for Worker instantiation recipes.
150
55
  */
151
56
  export declare function openDiplomaticClient(opts: OpenDiplomaticClientOptions): Promise<OpenedDiplomaticClient>;
152
57
  export {};
@@ -0,0 +1,17 @@
1
+ import { Status } from "../shared/consts";
2
+ import { type LargeBlobCreateOpts, type LargeBlobRp } from "../shared/webauthn/largeBlob";
3
+ import { type ValStat } from "../shared/valstat";
4
+ export type { LargeBlobCreateOpts, LargeBlobRp };
5
+ /**
6
+ * WebAuthn largeBlob byte I/O for non-seed opaque data.
7
+ * For seed/identity use {@link Enclave} largeBlob methods only.
8
+ */
9
+ export declare const LargeBlob: {
10
+ readonly capable: () => Promise<boolean>;
11
+ readonly createCred: (opts?: LargeBlobCreateOpts) => Promise<ValStat<Uint8Array>>;
12
+ /**
13
+ * Write arbitrary opaque bytes (UV). Do not pass unencrypted seed from app code.
14
+ */
15
+ readonly write: (credId: Uint8Array, data: Uint8Array, opts?: LargeBlobRp) => Promise<Status>;
16
+ readonly read: (credId: Uint8Array, opts?: LargeBlobRp) => Promise<ValStat<Uint8Array>>;
17
+ };
@@ -0,0 +1,62 @@
1
+ import { Enclave } from "../shared/crypto/enclave";
2
+ import { type SealedMasterKey } from "../shared/seed";
3
+ import type { ICrypto } from "../shared/types";
4
+ import { type ValStat } from "../shared/valstat";
5
+ import type { ISeedStore, SetSeedOpts } from "../types";
6
+ import type { PrfRp } from "../shared/webauthn/prf";
7
+ export type PrfSeedMeta = {
8
+ /** AEAD-sealed master under KDF(PRF). */
9
+ sealedMaster: SealedMasterKey;
10
+ /** PRF salt used at wrap time. */
11
+ salt: Uint8Array;
12
+ /** Optional allowCredentials id. */
13
+ credId?: Uint8Array;
14
+ };
15
+ /**
16
+ * Persist sealed-master metadata for cold start (e.g. protocol IDB seedMeta).
17
+ * Called with `undefined` when the store is wiped.
18
+ */
19
+ export type PersistPrfSeedMeta = (meta: PrfSeedMeta | undefined) => void | Promise<void>;
20
+ export type PrfSeedStoreOpts = PrfRp & {
21
+ crypto: ICrypto;
22
+ /**
23
+ * Required durable write path for sealed master + salt + credId.
24
+ * Protocol IDB implements this via {@link IDBSeedStore.persistPrfMeta}.
25
+ */
26
+ persistMeta: PersistPrfSeedMeta;
27
+ /** Previously persisted meta (cold start). Does not re-call persistMeta. */
28
+ meta?: PrfSeedMeta;
29
+ };
30
+ /**
31
+ * Session + durable PRF-wrapped seed. Session secret is always {@link Enclave}.
32
+ *
33
+ * - {@link save} is memory-only (holds enclave).
34
+ * - {@link wrapAndSave} runs PRF UV inside Enclave and persists sealed meta.
35
+ * - {@link unlock} runs PRF UV inside Enclave and returns a new enclave.
36
+ *
37
+ * Fallback when PRF is unavailable (not implemented yet): passphrase-sealed
38
+ * meta with the same layout — no plain seed on disk either way.
39
+ */
40
+ export declare class PrfSeedStore implements ISeedStore {
41
+ #private;
42
+ constructor(opts: PrfSeedStoreOpts);
43
+ get meta(): PrfSeedMeta | undefined;
44
+ setMeta(meta: PrfSeedMeta | undefined): void;
45
+ save(enclave: Enclave, opts?: SetSeedOpts): Promise<Enclave>;
46
+ /**
47
+ * PRF UV inside Enclave, seal master, persist meta (no PRF leaves Enclave).
48
+ */
49
+ wrapAndSave(enclave: Enclave, opts?: {
50
+ salt?: Uint8Array;
51
+ credId?: Uint8Array;
52
+ createCredIfNeeded?: boolean;
53
+ }): Promise<ValStat<Enclave>>;
54
+ load(): Promise<Enclave | void>;
55
+ /** Passkey UV inside Enclave → new session enclave. */
56
+ unlock(): Promise<ValStat<Enclave>>;
57
+ /**
58
+ * Install sealed meta + session enclave (e.g. after pair-package open) and persist.
59
+ */
60
+ adoptSealed(enclave: Enclave, meta: PrfSeedMeta): Promise<ValStat<Enclave>>;
61
+ wipe(): Promise<void>;
62
+ }
@@ -1,111 +1,25 @@
1
1
  import { Enclave } from "../shared/crypto/enclave";
2
- import type { MasterSeed } from "../shared/types";
2
+ import type { ICrypto } from "../shared/types";
3
+ import { type ValStat } from "../shared/valstat";
3
4
  import type { ISeedStore, SetSeedOpts } from "../types";
4
- export type LargeBlobRp = {
5
- rpId?: string;
6
- rpName?: string;
5
+ import { type LargeBlobRp } from "./largeBlob";
6
+ export { defaultWebAuthnRpId } from "./webauthn";
7
+ export { LargeBlob, type LargeBlobCreateOpts, type LargeBlobRp, } from "./largeBlob";
8
+ export type PasskeySeedStoreOpts = LargeBlobRp & {
9
+ crypto: ICrypto;
10
+ credId?: Uint8Array;
7
11
  };
8
12
  /**
9
- * Stable WebAuthn RP ID for a hostname: registrable domain (eTLD+1) when
10
- * possible, else the hostname itself.
11
- *
12
- * Using the full hostname (e.g. life.example.com) scopes credentials to that
13
- * host only. Safari/Apple Passwords often present the site as the apex domain;
14
- * Chrome + security keys tend to store whatever rpId we pass. Defaulting to
15
- * eTLD+1 keeps platform + roaming authenticators on the same RP ID across
16
- * subdomains and browsers.
17
- *
18
- * localhost / IPs are returned unchanged (valid only for those origins).
19
- */
20
- export declare function defaultWebAuthnRpId(hostname: string): string;
21
- /**
22
- * Best-effort capability probe (not all UAs expose this accurately).
23
- *
24
- * Safari 17+ supports largeBlob on platform authenticators but often omits or
25
- * mis-reports `extension:largeBlob` from {@link PublicKeyCredential.getClientCapabilities}.
26
- * Treat Apple Safari as capable so UI can offer enroll; create still checks
27
- * `largeBlob.supported` after the ceremony.
28
- */
29
- export declare function largeBlobCapable(): Promise<boolean>;
30
- export type LargeBlobCreateOpts = LargeBlobRp & {
31
- userName?: string;
32
- /**
33
- * Omit (default) to allow both platform (Hello/Touch ID) and roaming
34
- * authenticators (YubiKey). Set only if you intentionally restrict.
35
- */
36
- authenticatorAttachment?: AuthenticatorAttachment;
37
- };
38
- /**
39
- * Create a discoverable credential with largeBlob support.
40
- * Does **not** write the seed — call {@link writeLargeBlobSeed} from a
41
- * **separate user gesture** (second click). Safari / WebKit will not reliably
42
- * run two WebAuthn ceremonies from one button press; chaining create→write
43
- * leaves empty passkeys (see blobviem / nsatragno largeBlob demos).
44
- *
45
- * On Apple Safari, defaults to `authenticatorAttachment: "platform"` so
46
- * iCloud Keychain is preferred (Apple largeBlob is platform-oriented).
47
- * Pass `authenticatorAttachment: "cross-platform"` for security keys.
48
- */
49
- export declare function createLargeBlobCred(opts?: LargeBlobCreateOpts): Promise<Uint8Array>;
50
- /**
51
- * Persist seed on an existing largeBlob-capable credential.
52
- * Must be invoked from its **own** user gesture (not chained after create
53
- * in the same click handler on Safari).
54
- *
55
- * Spec: allowCredentials must contain exactly one credential for write.
56
- */
57
- export declare function writeLargeBlobSeed(credId: Uint8Array, seed: MasterSeed, opts?: LargeBlobRp): Promise<void>;
58
- /** Overwrite largeBlob with 32 zero bytes (destroys stored seed material). UV required. */
59
- export declare function clearLargeBlobSeed(credId: Uint8Array, opts?: LargeBlobRp): Promise<void>;
60
- export type LargeBlobUnlock = {
61
- seed: MasterSeed;
62
- /** Authenticator credential id — app should persist for faster local unlock. */
63
- credId: Uint8Array;
64
- };
65
- /**
66
- * Read seed from largeBlob on a known credential (user verification required).
67
- */
68
- export declare function readLargeBlobSeed(credId: Uint8Array, opts?: LargeBlobRp): Promise<MasterSeed>;
69
- /**
70
- * Same as {@link readLargeBlobSeed} but also returns the credential id used.
71
- */
72
- export declare function readLargeBlobUnlock(credId: Uint8Array, opts?: LargeBlobRp): Promise<LargeBlobUnlock>;
73
- /**
74
- * Discoverable assertion + largeBlob read (no local credential id).
75
- *
76
- * Omits `allowCredentials` so the platform can offer resident keys for this
77
- * rpId — including iCloud-synced passkeys. Returns seed and `credId` so the
78
- * app can cache the id for later non-discoverable unlocks.
79
- */
80
- export declare function discoverLargeBlobSeed(opts?: LargeBlobRp): Promise<LargeBlobUnlock>;
81
- /**
82
- * Create credential then write seed in one call.
83
- *
84
- * **Safari/WebKit:** prefer {@link createLargeBlobCred} + {@link writeLargeBlobSeed}
85
- * from **two separate clicks**. Chaining both after one gesture often creates a
86
- * passkey without a blob (`written` false / later `missing blob` on read).
87
- *
88
- * Returns credential id — persist it; the seed lives on the authenticator.
89
- */
90
- export declare function storeSeedLargeBlob(seed: MasterSeed, opts?: LargeBlobCreateOpts): Promise<Uint8Array>;
91
- /**
92
- * ISeedStore backed by largeBlob.
93
- * - {@link save} creates (or reuses) cred + writes seed (gestures).
94
- * - {@link load} returns in-memory enclave only; call {@link unlock} after restart.
95
- * - Local {@link credId} must be persisted by the app across sessions.
96
- * - {@link wipe} overwrites largeBlob with zeros (UV), then drops memory + credId.
97
- * Does not delete the WebAuthn credential from the authenticator.
13
+ * ISeedStore backed by largeBlob. Session secret is always {@link Enclave}.
14
+ * Persist uses {@link Enclave.persistToLargeBlob} / {@link Enclave.fromLargeBlob}.
98
15
  */
99
16
  export declare class PasskeySeedStore implements ISeedStore {
100
17
  #private;
101
- constructor(opts?: LargeBlobRp & {
102
- credId?: Uint8Array;
103
- });
18
+ constructor(opts: PasskeySeedStoreOpts);
104
19
  get credId(): Uint8Array | undefined;
105
20
  setCredId(credId: Uint8Array | undefined): void;
106
- save(seed: MasterSeed, opts?: SetSeedOpts): Promise<Enclave>;
21
+ save(enclave: Enclave, opts?: SetSeedOpts): Promise<Enclave>;
107
22
  load(): Promise<Enclave | void>;
108
- /** User-gesture unlock after cold start (needs {@link credId}). */
109
- unlock(): Promise<Enclave>;
23
+ unlock(): Promise<ValStat<Enclave>>;
110
24
  wipe(): Promise<void>;
111
25
  }
@@ -0,0 +1 @@
1
+ export { asPublicKeyCredential, checkWebAuthn, copyToArrayBuffer, defaultWebAuthnRpId, resolveWebAuthnRpId, webAuthnExtensionCapable, COSE_ALG_EDDSA, COSE_ALG_ES256, COSE_ALG_RS256, WEBAUTHN_CHAL_LEN, WEBAUTHN_PUB_KEY_PARAMS, type WebAuthnRp, } from "../shared/webauthn/common";
@@ -1,46 +1,39 @@
1
1
  import { IEntDB } from "../entdb/entdb";
2
2
  import { IClock } from "../shared/clock";
3
- import { IHostConnectionInfo, IStateManager, MasterSeed } from "../shared/types";
3
+ import type { Enclave } from "../shared/crypto/enclave";
4
+ import { IHostConnectionInfo, IStateManager } from "../shared/types";
4
5
  import type { IClient, IDiplomaticClientState, IDiplomaticClientXferState, IStore } from "../types";
5
6
  export declare function useClientState(client: Pick<IClient<URL>, "clientState">): IDiplomaticClientState | undefined;
6
7
  export declare function useClientXferState(client: Pick<IClient<URL>, "xferState">): IDiplomaticClientXferState | undefined;
7
8
  export declare function useSyncOnResume(client: Pick<IClient<URL>, "connect" | "sync">): void;
8
9
  type UseClientBase = {
9
10
  clock?: IClock;
10
- seed?: MasterSeed;
11
+ /** Session enclave (master seed stays inside Enclave). */
12
+ seed?: Enclave;
11
13
  host?: IHostConnectionInfo<URL>;
12
14
  readyTimeoutMs?: number;
13
15
  };
14
16
  /**
15
- * Worker path: always IndexedDB on main + worker. Custom `store` is forbidden
16
- * (type + runtime) so durable state cannot diverge.
17
+ * Worker path: IndexedDB on main; sync Worker appears on setSeed via Enclave.
18
+ * Custom `store` is forbidden so durable state cannot diverge.
17
19
  *
18
- * Vite:
19
- * import DiplomaticWorker from "@interncom/diplomatic/worker?worker";
20
- * const syncWorker = new DiplomaticWorker();
21
- * useClient({ worker: syncWorker, seed, host });
22
- *
23
- * Handshake is race-safe (ready event and/or probe ping). See
24
- * `openDiplomaticClient` for bundler recipes and handshake notes.
20
+ * ```ts
21
+ * useClient({ worker: true, seed, host });
22
+ * ```
25
23
  */
26
24
  export type UseClientWorkerOptions = UseClientBase & {
27
- /**
28
- * App-constructed sync Worker. Create once (module scope or useMemo/useRef),
29
- * not each render. Early construction before `useClient` opens IDB is OK —
30
- * the library does not require catching the unsolicited `ready` event.
31
- * See `openDiplomaticClient` for instantiation recipes.
32
- */
33
- worker: Worker;
25
+ /** Worker-mode client; Enclave.spawnSyncWorker runs inside setSeed. */
26
+ worker: true;
34
27
  store?: never;
35
28
  };
36
29
  /**
37
30
  * Main-thread path. Optional custom store; default IndexedDB.
38
31
  */
39
32
  export type UseClientMainOptions = UseClientBase & {
40
- worker?: undefined;
33
+ worker?: undefined | false;
41
34
  /**
42
35
  * Protocol store override. Default: IndexedDB. Pass explicitly for
43
- * MemoryStore or other backends. Incompatible with `worker`.
36
+ * MemoryStore or other backends. Incompatible with `worker: true`.
44
37
  */
45
38
  store?: IStore<URL>;
46
39
  };
@@ -3,6 +3,10 @@ export declare function htob(hex: string): Uint8Array;
3
3
  export declare function bytesEqual(a: Uint8Array, b: Uint8Array): boolean;
4
4
  export declare let btob64: (bytes: Uint8Array) => string;
5
5
  export declare let b64tob: (b64: string) => Uint8Array;
6
+ /** Bytes → base64url (no padding). For string transport (pair packages, QR payloads). */
7
+ export declare function btob64url(bytes: Uint8Array): string;
8
+ /** Base64url (padding optional) → bytes. */
9
+ export declare function b64urltob(b64url: string): Uint8Array;
6
10
  export declare function concat(a: Uint8Array, b: Uint8Array): Uint8Array;
7
11
  export declare function btob128(bytes: Uint8Array): string;
8
12
  export declare function b128tob(str: string): Uint8Array;
@@ -0,0 +1,11 @@
1
+ import { ICodecStruct } from "../codec.ts";
2
+ import type { IHostConnectionInfo } from "../types.ts";
3
+ /**
4
+ * Wire form of {@link IHostConnectionInfo}: same fields, handle as string URL
5
+ * (live clients use `URL | IProtoHost`).
6
+ */
7
+ export type BundleHost = Omit<IHostConnectionInfo<URL>, "handle"> & {
8
+ handle: string;
9
+ };
10
+ /** handle (varstring), label (varstring), idx (varint; default 0). */
11
+ export declare const bundleHostCodec: ICodecStruct<BundleHost>;
@@ -0,0 +1,25 @@
1
+ import { ICodecStruct } from "../codec.ts";
2
+ import { type MasterSeed } from "../seed.ts";
3
+ import { type ValStat } from "../valstat.ts";
4
+ import { type BundleHost } from "./bundleHost.ts";
5
+ export { type BundleHost, bundleHostCodec } from "./bundleHost.ts";
6
+ export declare const IDENTITY_BUNDLE_VERSION = 1;
7
+ export type IdentityBundle = {
8
+ v: number;
9
+ masterSeed: MasterSeed;
10
+ hosts: BundleHost[];
11
+ };
12
+ /**
13
+ * IdentityBundle wire layout:
14
+ * v: varint
15
+ * masterSeed: 32 fixed bytes
16
+ * hostsLen: varint
17
+ * hosts: hostsLen × BundleHost
18
+ */
19
+ export declare const identityBundleCodec: ICodecStruct<IdentityBundle>;
20
+ /**
21
+ * Build a wire-format identity bundle from a branded {@link MasterSeed} + hosts.
22
+ * Codec/tests only. Runtime seed I/O must go through Enclave.persistToLargeBlob
23
+ * — never encode then hand seed-bearing bytes to outer layers.
24
+ */
25
+ export declare function createIdentityBundle(masterSeed: MasterSeed, hosts: BundleHost[]): ValStat<IdentityBundle>;
@@ -0,0 +1,15 @@
1
+ import { ICodecStruct } from "../codec.ts";
2
+ import { type MasterSeed } from "../seed.ts";
3
+ import { type BundleHost } from "./bundleHost.ts";
4
+ export type PairPackagePlain = {
5
+ masterSeed: MasterSeed;
6
+ hosts: BundleHost[];
7
+ };
8
+ /**
9
+ * Inner plaintext body (not sealed here — AEAD is applied by the pairing
10
+ * ceremony, then carried as `body` in {@link pairPackageEnvelopeCodec}):
11
+ * masterSeed: 32 fixed bytes
12
+ * hostsLen: varint
13
+ * hosts: hostsLen × BundleHost
14
+ */
15
+ export declare const pairPackagePlainCodec: ICodecStruct<PairPackagePlain>;
@@ -0,0 +1,18 @@
1
+ import { ICodecStruct } from "../codec.ts";
2
+ export declare const PAIR_PACKAGE_VERSION = 1;
3
+ export type PairPackageEnvelope = {
4
+ v: number;
5
+ salt: Uint8Array;
6
+ /** Empty when absent. */
7
+ credId: Uint8Array;
8
+ /** AEAD ciphertext of pairPackagePlainCodec bytes under KDF(PRF). */
9
+ body: Uint8Array;
10
+ };
11
+ /**
12
+ * Outer envelope (metadata is not secret; body is AEAD ciphertext):
13
+ * v: varint
14
+ * salt: varbytes
15
+ * credId: varbytes (length 0 if none)
16
+ * body: varbytes (AEAD of pair-package plain)
17
+ */
18
+ export declare const pairPackageEnvelopeCodec: ICodecStruct<PairPackageEnvelope>;
@@ -1,4 +1,26 @@
1
- import type { ICrypto, MasterSeed, PublicKey } from "../types.ts";
1
+ import type { BundleHost } from "../codecs/bundleHost.ts";
2
+ import { Status } from "../consts.ts";
3
+ import { MASTER_SEED_LEN, type SealedMasterKey } from "../seed.ts";
4
+ import type { ICrypto, PublicKey } from "../types.ts";
5
+ import { type ValStat } from "../valstat.ts";
6
+ import { type LargeBlobCreateOpts, type LargeBlobRp } from "../webauthn/largeBlob.ts";
7
+ import { type PrfCreateOpts, type PrfEvalOpts, type PrfRp } from "../webauthn/prf.ts";
8
+ export type { LargeBlobCreateOpts, LargeBlobRp };
9
+ export type { PrfCreateOpts, PrfEvalOpts, PrfRp };
10
+ /** Options for PRF seal/unseal ceremonies (no raw PRF bytes). */
11
+ export type PasskeyPrfOpts = PrfRp & {
12
+ salt?: Uint8Array;
13
+ credId?: Uint8Array;
14
+ /** Seal only: create a PRF credential first when no credId is known. */
15
+ createCredIfNeeded?: boolean;
16
+ userName?: string;
17
+ };
18
+ /** Durable PRF-sealed master + public meta (never includes PRF output). */
19
+ export type PrfSealedMaster = {
20
+ sealedMaster: SealedMasterKey;
21
+ salt: Uint8Array;
22
+ credId: Uint8Array;
23
+ };
2
24
  export type EncryptCipher = {
3
25
  encrypt: (data: Uint8Array) => Promise<Uint8Array>;
4
26
  };
@@ -25,9 +47,95 @@ export type Identity = {
25
47
  /** Per-bag KDM mixed with this identity's private key (see bag seal). */
26
48
  kdmFor: (msgHeadEnc: Uint8Array) => Promise<Uint8Array>;
27
49
  };
50
+ declare const SEAL_KEY_LEN = 32;
51
+ declare const SEAL_PRF_MIN_LEN = 16;
28
52
  export declare class Enclave {
29
53
  #private;
30
- constructor(seed: MasterSeed, crypto: ICrypto);
54
+ private constructor();
55
+ /** New enclave with a fresh 32-byte master seed. */
56
+ static fromRandom(crypto: ICrypto): Promise<Enclave>;
57
+ /**
58
+ * Construct from raw master seed bytes (import / bootstrap only).
59
+ * Copies the seed so callers may zero their buffer after success.
60
+ * Prefer {@link fromRandom} or {@link unsealWithPasskey} for normal flows.
61
+ */
62
+ static fromBytes(crypto: ICrypto, bytes: Uint8Array): ValStat<Enclave>;
63
+ /**
64
+ * UV + PRF ceremony, then AEAD-seal master. Returns sealed ciphertext + public
65
+ * meta only — never PRF output. Sealed blob on disk is useless without a
66
+ * ceremony Enclave itself runs on unlock.
67
+ *
68
+ * Future: sealWithPassphrase when PRF is unavailable (same sealed layout).
69
+ */
70
+ sealWithPasskey(opts?: PasskeyPrfOpts): Promise<ValStat<PrfSealedMaster>>;
71
+ /**
72
+ * UV + PRF ceremony, then unseal durable master into a new Enclave.
73
+ * Callers never supply PRF bytes — only sealed meta + RP/salt/credId.
74
+ */
75
+ static unsealWithPasskey(crypto: ICrypto, sealedMaster: SealedMasterKey, opts: PasskeyPrfOpts & {
76
+ salt: Uint8Array;
77
+ }): Promise<ValStat<Enclave>>;
78
+ /**
79
+ * UV + write IdentityBundle (seed + hosts) to largeBlob.
80
+ * Encode and WebAuthn write complete inside this method; nothing seed-bearing
81
+ * is returned. Prefer empty `hosts` for seed-only backup.
82
+ *
83
+ * Pass `opts.credId` to write an existing credential (one UV). Omit it to
84
+ * create a largeBlob-capable credential then write (two UV). Returns the
85
+ * credential id used.
86
+ */
87
+ persistToLargeBlob(hosts?: BundleHost[], opts?: LargeBlobCreateOpts & {
88
+ credId?: Uint8Array;
89
+ }): Promise<ValStat<Uint8Array>>;
90
+ /**
91
+ * UV + read largeBlob payload into a new Enclave (+ hosts if IdentityBundle).
92
+ * Seed material never leaves this factory.
93
+ *
94
+ * Pass `opts.credId` for a known credential; omit for discoverable assertion.
95
+ * Returns the credential id used (useful after discover).
96
+ */
97
+ static fromLargeBlob(crypto: ICrypto, opts?: LargeBlobRp & {
98
+ credId?: Uint8Array;
99
+ }): Promise<ValStat<{
100
+ enclave: Enclave;
101
+ hosts: BundleHost[];
102
+ credId: Uint8Array;
103
+ }>>;
104
+ /** UV + overwrite largeBlob with zeros (destroys stored seed material). */
105
+ static clearLargeBlob(credId: Uint8Array, opts?: LargeBlobRp): Promise<Status>;
106
+ /**
107
+ * UV + PRF, then AEAD-seal pair-package plaintext (seed + hosts).
108
+ * Returns ciphertext body + public ceremony meta (no PRF).
109
+ */
110
+ sealPairPackageBody(hosts: BundleHost[], opts?: PasskeyPrfOpts): Promise<ValStat<{
111
+ body: Uint8Array;
112
+ salt: Uint8Array;
113
+ credId: Uint8Array;
114
+ }>>;
115
+ /**
116
+ * UV + PRF (using envelope salt/credId), decrypt pair body, build Enclave.
117
+ * Also seals master under the same PRF for durable IDB (single ceremony).
118
+ */
119
+ static openPairPackageBody(crypto: ICrypto, body: Uint8Array, opts: PasskeyPrfOpts & {
120
+ salt: Uint8Array;
121
+ }): Promise<ValStat<{
122
+ enclave: Enclave;
123
+ hosts: BundleHost[];
124
+ sealedMaster: SealedMasterKey;
125
+ credId: Uint8Array;
126
+ }>>;
127
+ /**
128
+ * Spawn a new sync Worker from the embedded bundle and inject this enclave's
129
+ * master seed into it (transfer). Seed never goes to a caller-supplied port —
130
+ * only to a worker this method just created.
131
+ *
132
+ * Returns the Worker. Caller (WorkerClient) should adopt it and await the
133
+ * `setSeed` reply for `opts.id`.
134
+ */
135
+ spawnSyncWorker(opts: {
136
+ id: number;
137
+ persist?: boolean;
138
+ }): Worker;
31
139
  /**
32
140
  * Cipher for public KDM. Does not hold key bytes; each op re-enters the
33
141
  * enclave (async, hardware-shaped). `usage` selects the return shape:
@@ -40,3 +148,6 @@ export declare class Enclave {
40
148
  */
41
149
  deriveIdentity(keyPath: string, idx?: number): Promise<Identity>;
42
150
  }
151
+ /** Derive seal key from PRF output (domain-separated). Used by enclave seal/unseal. */
152
+ export declare function sealKeyFromPrf(crypto: ICrypto, prf: Uint8Array): Promise<ValStat<Uint8Array>>;
153
+ export { MASTER_SEED_LEN, SEAL_KEY_LEN, SEAL_PRF_MIN_LEN };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Fill and return `n` cryptographically secure random bytes.
3
+ */
4
+ export declare function randomBytes(n: number): Uint8Array;
5
+ /**
6
+ * Same as {@link randomBytes}, typed as `Uint8Array<ArrayBuffer>` for DOM
7
+ * `BufferSource` APIs (e.g. WebAuthn challenges and user ids).
8
+ */
9
+ export declare function randomBytesArrayBuffer(n: number): Uint8Array<ArrayBuffer>;
10
+ /** 32 cryptographically secure random bytes (e.g. master seed material). */
11
+ export declare function random256BitSeed(): Uint8Array;