@nihilium/recovery-service 0.0.0-stage → 0.9.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.
package/README.md CHANGED
@@ -1,3 +1,80 @@
1
- # Temporary Holding Version
1
+ # `@nihilium/recovery-service`
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The host half of value decomposition (spec §12). A wallet provider runs this and holds a recovery
4
+ vault's **encrypted records**; the seal that makes them meaningful stays with the user.
5
+
6
+ What the provider holds is inert on its own, so it has no unilateral access path — which is what
7
+ lets the same party hold operational controls, the §6 pause key included, without holding custody.
8
+
9
+ ## What it deliberately cannot do
10
+
11
+ - **List.** There is no listing route and no `listRecords` on `SealedDataStore`. A listing would make
12
+ the provider a directory of who has a recovery.
13
+ - **Distinguish an empty record from an absent one.** An unknown id returns `{ entries: [] }`, not a
14
+ 404, so a prober cannot enumerate.
15
+ - **Read what it holds.** The record payload is adapter-owned ciphertext and is never inspected —
16
+ including its format tag, so a condition adapter written next year works against a service
17
+ deployed today.
18
+
19
+ ## Node-only by resolution
20
+
21
+ The `exports` map declares `types` and `node` and deliberately no `browser` or `default` condition,
22
+ mirroring `@nihilium/client-sdk`'s `./server` entry. A wallet bundle that imports this **fails to
23
+ resolve** rather than quietly shipping the provider's storage side to the client.
24
+
25
+ ## Capabilities
26
+
27
+ | | Who holds it | Mailable |
28
+ |---|---|---|
29
+ | `recordId` | the user, the provider, anyone | **yes** — that is what it is for |
30
+ | append credential | the wallet, beside the seal | **no** — it is a write capability |
31
+ | the seal | the user only | **no** — it is bearer material |
32
+
33
+ Reading needs only the record id: 120 unguessable bits yielding ciphertext. Appending needs a
34
+ separate credential, because an add-only store cannot delete — a read capability that also wrote
35
+ would make a leaked email a permanent junk-injection vector.
36
+
37
+ ## Wiring it up
38
+
39
+ ```ts
40
+ import { createServer } from "node:http";
41
+ import { LocalSealedDataStore } from "@nihilium/recovery-storage-local";
42
+ import { RecoveryDataService, hashCredential, sharedSecretAppend } from "@nihilium/recovery-service";
43
+
44
+ const service = new RecoveryDataService({
45
+ store: new LocalSealedDataStore({ directory: "/var/lib/nihilium", domain: "acme-wallet" }),
46
+ append: sharedSecretAppend({ [recordId]: hashCredential(credential) }),
47
+ });
48
+
49
+ createServer(async (req, res) => {
50
+ const chunks: Buffer[] = [];
51
+ for await (const chunk of req) chunks.push(chunk as Buffer);
52
+ const body = chunks.length ? JSON.parse(Buffer.concat(chunks).toString()) : undefined;
53
+
54
+ const { status, body: out } = await service.handle({
55
+ method: req.method!,
56
+ path: new URL(req.url!, "http://localhost").pathname,
57
+ headers: req.headers as Record<string, string>,
58
+ body,
59
+ });
60
+ res.writeHead(status, { "content-type": "application/json" });
61
+ res.end(out === null ? "" : JSON.stringify(out));
62
+ }).listen(8080);
63
+ ```
64
+
65
+ Embedding it in a framework you already run? Call `listEntries` and `appendEntry` directly —
66
+ `handle()` is a thin router over them, not the API.
67
+
68
+ ## Routes
69
+
70
+ ```
71
+ GET /records/:id -> 200 { entries } no credential
72
+ POST /records/:id -> 204 X-Nihilium-Append-Credential required
73
+ ```
74
+
75
+ ## The failure mode to plan for
76
+
77
+ Losing these records loses the recovery even with the seal in hand. Because a record is inert
78
+ without the seal, there is no reason not to keep copies everywhere — keep one on the user's device
79
+ and treat the provider as a **replica, never the origin**. That is the exact inverse of the seal's
80
+ placement rule, and it is deliberate.
@@ -0,0 +1,118 @@
1
+ import { type SealedDataEntry, type SealedDataStore, type ServiceRequest, type ServiceResponse } from "@nihilium/recovery-core";
2
+ /**
3
+ * Re-exported so this package's surface is unchanged by the move into core.
4
+ *
5
+ * `ServiceError`, `hashCredential` and the constant-time compare are shared with
6
+ * `@nihilium/recovery-watchtower`: two hosts must not carry two copies of the same security
7
+ * primitive, since copies drift and nobody reading either one can see it.
8
+ */
9
+ export { ServiceError, constantTimeEquals, hashCredential, readHeader, } from "@nihilium/recovery-core";
10
+ export type { ServiceRequest, ServiceResponse } from "@nihilium/recovery-core";
11
+ /**
12
+ * `@nihilium/recovery-service` — the host half of value decomposition (spec §12).
13
+ *
14
+ * A wallet provider runs this and holds a vault's encrypted records. What it holds is meaningless
15
+ * without the seal, which lives with the user, so the provider has **no unilateral access path** —
16
+ * which is exactly what lets the same party hold operational controls, the §6 pause key included,
17
+ * without ever holding custody.
18
+ *
19
+ * **Node-only, by resolution rather than by convention.** The package's `exports` map declares
20
+ * `types` and `node` and deliberately no `browser` or `default` condition, mirroring
21
+ * `@nihilium/client-sdk`'s `./server` entry. A wallet bundle that imports this fails to resolve
22
+ * instead of quietly shipping the provider's storage side to the client.
23
+ *
24
+ * There is no `node:http` here either. The package stays runtime-agnostic and a host wires
25
+ * `handle()` into whatever server it already runs — see the README for the five-line Node version.
26
+ */
27
+ /** Decides whether an append may proceed. See `sharedSecretAppend` for the default. */
28
+ export interface AppendAuthorizer {
29
+ authorize(params: {
30
+ recordId: string;
31
+ credential: string | undefined;
32
+ }): Promise<void> | void;
33
+ }
34
+ export interface RecoveryDataServiceOptions {
35
+ store: SealedDataStore;
36
+ append: AppendAuthorizer;
37
+ /**
38
+ * Largest record accepted, in bytes of serialized JSON.
39
+ *
40
+ * A single-condition record is a few hundred bytes; an n-member quorum record is n times that
41
+ * plus manifest. 64 KiB is generous for both. Without a cap, an add-only store behind a bearer
42
+ * id is a free unbounded object store.
43
+ */
44
+ maxEntryBytes?: number;
45
+ /**
46
+ * Most entries one record may hold. A vault protecting more than a couple of hundred chains is
47
+ * not a thing; an append credential that leaked and is filling the record is.
48
+ */
49
+ maxEntriesPerRecord?: number;
50
+ }
51
+ /** Header the default authorizer reads the append credential from. */
52
+ export declare const APPEND_CREDENTIAL_HEADER = "x-nihilium-append-credential";
53
+ /**
54
+ * The host.
55
+ *
56
+ * Two layers on purpose: `listEntries`/`appendEntry` are the semantic API and are what a host
57
+ * embedding this in its own framework should call, while `handle()` is a thin router over them for
58
+ * hosts that want the HTTP shape handed to them.
59
+ */
60
+ export declare class RecoveryDataService {
61
+ private readonly store;
62
+ private readonly append;
63
+ private readonly maxEntryBytes;
64
+ private readonly maxEntriesPerRecord;
65
+ constructor(options: RecoveryDataServiceOptions);
66
+ /**
67
+ * Every entry under `recordId`.
68
+ *
69
+ * The record id **is** the read capability: 120 unguessable bits, and what it yields is
70
+ * ciphertext with no unilateral access path. No credential is required, which is what makes an
71
+ * id safe to mail to a user for later lookup.
72
+ *
73
+ * An unknown id returns an empty list rather than 404, deliberately. A prober must not be able
74
+ * to tell "this vault exists but is empty" from "no such vault" — the store behaves the same
75
+ * way, and a 404 here would undo it.
76
+ */
77
+ listEntries(recordId: string): Promise<SealedDataEntry[]>;
78
+ /**
79
+ * Append one record.
80
+ *
81
+ * The record id is deliberately **not** sufficient here. It is designed to be emailed, and an
82
+ * add-only store cannot delete, so a read capability that also wrote would be a permanent
83
+ * junk-injection vector with no way to clean up after it.
84
+ */
85
+ appendEntry(recordId: string, entry: SealedDataEntry, credential?: string): Promise<void>;
86
+ /**
87
+ * Route a request.
88
+ *
89
+ * Exactly two routes, and the absence of a third is a design decision rather than an omission:
90
+ * there is **no listing endpoint**. A listing would turn the service into a directory of who has
91
+ * a recovery, which is the one thing a provider holding only ciphertext must not become.
92
+ *
93
+ * GET /records/:id -> { entries }
94
+ * POST /records/:id -> 204, with an append credential
95
+ */
96
+ handle(req: ServiceRequest): Promise<ServiceResponse>;
97
+ }
98
+ /**
99
+ * Appends are authorized by a credential the wallet minted at seal time and kept beside the seal.
100
+ *
101
+ * Only `sha256(credential)` is stored, so a compromise of the service's own state does not yield a
102
+ * write capability — the same commitment discipline the quorum uses for shares. Compared in constant
103
+ * time, since a timing oracle on a hash comparison is cheap to remove and awkward to reason about
104
+ * later.
105
+ *
106
+ * The credential is a *write* capability and must never be mailed the way a record id is.
107
+ */
108
+ export declare function sharedSecretAppend(credentialHashes: Readonly<Record<string, string>>): AppendAuthorizer;
109
+ /**
110
+ * Lets anyone holding the record id append.
111
+ *
112
+ * Named to read badly in a configuration file, as `NihiliumPaymentProviderClientAPIKEY_DO_NOT_USE`
113
+ * is. Legitimate for the first-implementation case where the service and the seal sit on the same
114
+ * device and the record id never leaves it. Illegitimate the moment the id is mailed: it is designed
115
+ * to be, and an add-only store has no way to remove what a leaked id then writes.
116
+ */
117
+ export declare function openAppend_DO_NOT_USE_IN_PRODUCTION(): AppendAuthorizer;
118
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAQH,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,eAAe,EACvB,MAAM,yBAAyB,CAAC;AAEjC;;;;;;GAMG;AACH,OAAO,EACH,YAAY,EACZ,kBAAkB,EAClB,cAAc,EACd,UAAU,GACb,MAAM,yBAAyB,CAAC;AACjC,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAE/E;;;;;;;;;;;;;;;GAeG;AAIH,uFAAuF;AACvF,MAAM,WAAW,gBAAgB;IAC7B,SAAS,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACjG;AAED,MAAM,WAAW,0BAA0B;IACvC,KAAK,EAAE,eAAe,CAAC;IACvB,MAAM,EAAE,gBAAgB,CAAC;IACzB;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAChC;AAED,sEAAsE;AACtE,eAAO,MAAM,wBAAwB,iCAAiC,CAAC;AAKvE;;;;;;GAMG;AACH,qBAAa,mBAAmB;IAC5B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAkB;IACxC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmB;IAC1C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAS;gBAEjC,OAAO,EAAE,0BAA0B;IAO/C;;;;;;;;;;OAUG;IACG,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAI/D;;;;;;OAMG;IACG,WAAW,CACb,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,eAAe,EACtB,UAAU,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,IAAI,CAAC;IAyBhB;;;;;;;;;OASG;IACG,MAAM,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC;CA+B9D;AAID;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAC9B,gBAAgB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GACnD,gBAAgB,CAclB;AAED;;;;;;;GAOG;AACH,wBAAgB,mCAAmC,IAAI,gBAAgB,CAEtE"}
package/dist/index.js ADDED
@@ -0,0 +1,164 @@
1
+ import { RecoverySdkError, ServiceError, constantTimeEquals, describeEntryShapeProblem, hashCredential, normalizeRecordId, readHeader, } from "@nihilium/recovery-core";
2
+ /**
3
+ * Re-exported so this package's surface is unchanged by the move into core.
4
+ *
5
+ * `ServiceError`, `hashCredential` and the constant-time compare are shared with
6
+ * `@nihilium/recovery-watchtower`: two hosts must not carry two copies of the same security
7
+ * primitive, since copies drift and nobody reading either one can see it.
8
+ */
9
+ export { ServiceError, constantTimeEquals, hashCredential, readHeader, } from "@nihilium/recovery-core";
10
+ /** Header the default authorizer reads the append credential from. */
11
+ export const APPEND_CREDENTIAL_HEADER = "x-nihilium-append-credential";
12
+ const DEFAULT_MAX_ENTRY_BYTES = 64 * 1024;
13
+ const DEFAULT_MAX_ENTRIES = 256;
14
+ /**
15
+ * The host.
16
+ *
17
+ * Two layers on purpose: `listEntries`/`appendEntry` are the semantic API and are what a host
18
+ * embedding this in its own framework should call, while `handle()` is a thin router over them for
19
+ * hosts that want the HTTP shape handed to them.
20
+ */
21
+ export class RecoveryDataService {
22
+ store;
23
+ append;
24
+ maxEntryBytes;
25
+ maxEntriesPerRecord;
26
+ constructor(options) {
27
+ this.store = options.store;
28
+ this.append = options.append;
29
+ this.maxEntryBytes = options.maxEntryBytes ?? DEFAULT_MAX_ENTRY_BYTES;
30
+ this.maxEntriesPerRecord = options.maxEntriesPerRecord ?? DEFAULT_MAX_ENTRIES;
31
+ }
32
+ /**
33
+ * Every entry under `recordId`.
34
+ *
35
+ * The record id **is** the read capability: 120 unguessable bits, and what it yields is
36
+ * ciphertext with no unilateral access path. No credential is required, which is what makes an
37
+ * id safe to mail to a user for later lookup.
38
+ *
39
+ * An unknown id returns an empty list rather than 404, deliberately. A prober must not be able
40
+ * to tell "this vault exists but is empty" from "no such vault" — the store behaves the same
41
+ * way, and a 404 here would undo it.
42
+ */
43
+ async listEntries(recordId) {
44
+ return this.store.getEntries(normalizeRecordId(recordId));
45
+ }
46
+ /**
47
+ * Append one record.
48
+ *
49
+ * The record id is deliberately **not** sufficient here. It is designed to be emailed, and an
50
+ * add-only store cannot delete, so a read capability that also wrote would be a permanent
51
+ * junk-injection vector with no way to clean up after it.
52
+ */
53
+ async appendEntry(recordId, entry, credential) {
54
+ const id = normalizeRecordId(recordId);
55
+ await this.append.authorize({ recordId: id, credential });
56
+ assertEntryShape(entry);
57
+ const size = JSON.stringify(entry).length;
58
+ if (size > this.maxEntryBytes) {
59
+ throw new ServiceError(413, `Record is ${size} bytes; this service accepts at most ${this.maxEntryBytes}.`);
60
+ }
61
+ const existing = await this.store.getEntries(id);
62
+ if (existing.length >= this.maxEntriesPerRecord) {
63
+ throw new ServiceError(409, `This record already holds ${existing.length} entries, the configured maximum. ` +
64
+ `Entries are never removed, so this limit is final for this record.`);
65
+ }
66
+ await this.store.addEntry(id, entry);
67
+ }
68
+ /**
69
+ * Route a request.
70
+ *
71
+ * Exactly two routes, and the absence of a third is a design decision rather than an omission:
72
+ * there is **no listing endpoint**. A listing would turn the service into a directory of who has
73
+ * a recovery, which is the one thing a provider holding only ciphertext must not become.
74
+ *
75
+ * GET /records/:id -> { entries }
76
+ * POST /records/:id -> 204, with an append credential
77
+ */
78
+ async handle(req) {
79
+ try {
80
+ const recordId = recordIdFromPath(req.path);
81
+ if (recordId === undefined) {
82
+ return { status: 404, body: { error: "No such route." } };
83
+ }
84
+ if (req.method.toUpperCase() === "GET") {
85
+ return { status: 200, body: { entries: await this.listEntries(recordId) } };
86
+ }
87
+ if (req.method.toUpperCase() === "POST") {
88
+ await this.appendEntry(recordId, req.body, readHeader(req, APPEND_CREDENTIAL_HEADER));
89
+ return { status: 204, body: null };
90
+ }
91
+ return { status: 405, body: { error: `${req.method} is not allowed here.` } };
92
+ }
93
+ catch (error) {
94
+ if (error instanceof ServiceError) {
95
+ return { status: error.status, body: { error: error.message } };
96
+ }
97
+ if (error instanceof RecoverySdkError) {
98
+ // A malformed record id, a reused entry id, a store refusing a write. All of these
99
+ // are the caller's fault and all carry messages written to be read.
100
+ return { status: 400, body: { error: error.message } };
101
+ }
102
+ throw error;
103
+ }
104
+ }
105
+ }
106
+ // --- Append authorization -------------------------------------------------------------------------
107
+ /**
108
+ * Appends are authorized by a credential the wallet minted at seal time and kept beside the seal.
109
+ *
110
+ * Only `sha256(credential)` is stored, so a compromise of the service's own state does not yield a
111
+ * write capability — the same commitment discipline the quorum uses for shares. Compared in constant
112
+ * time, since a timing oracle on a hash comparison is cheap to remove and awkward to reason about
113
+ * later.
114
+ *
115
+ * The credential is a *write* capability and must never be mailed the way a record id is.
116
+ */
117
+ export function sharedSecretAppend(credentialHashes) {
118
+ return {
119
+ authorize({ recordId, credential }) {
120
+ const expected = credentialHashes[recordId];
121
+ if (!expected) {
122
+ // Same status and message whether the record is unknown or the credential is wrong,
123
+ // so a prober cannot use append failures to enumerate records.
124
+ throw new ServiceError(403, "Not authorized to append to this record.");
125
+ }
126
+ if (credential === undefined || !constantTimeEquals(hashCredential(credential), expected)) {
127
+ throw new ServiceError(403, "Not authorized to append to this record.");
128
+ }
129
+ },
130
+ };
131
+ }
132
+ /**
133
+ * Lets anyone holding the record id append.
134
+ *
135
+ * Named to read badly in a configuration file, as `NihiliumPaymentProviderClientAPIKEY_DO_NOT_USE`
136
+ * is. Legitimate for the first-implementation case where the service and the seal sit on the same
137
+ * device and the record id never leaves it. Illegitimate the moment the id is mailed: it is designed
138
+ * to be, and an add-only store has no way to remove what a leaked id then writes.
139
+ */
140
+ export function openAppend_DO_NOT_USE_IN_PRODUCTION() {
141
+ return { authorize() { } };
142
+ }
143
+ // --- Internals ------------------------------------------------------------------------------------
144
+ /** `/records/:id`, with or without a trailing slash. Anything else is not a route. */
145
+ function recordIdFromPath(path) {
146
+ const parts = path.split("/").filter((part) => part !== "");
147
+ if (parts.length !== 2 || parts[0] !== "records")
148
+ return undefined;
149
+ return decodeURIComponent(parts[1]);
150
+ }
151
+ /**
152
+ * Check what the service can check without being able to read anything.
153
+ *
154
+ * The rule itself lives in core as `describeEntryShapeProblem`, because a client applies the same
155
+ * one to a *response* body where a `400` would be nonsense. What stays here is the service's error
156
+ * policy: the shape is a client's fault, so it is a `400`.
157
+ */
158
+ function assertEntryShape(entry) {
159
+ const problem = describeEntryShapeProblem(entry);
160
+ if (problem !== undefined) {
161
+ throw new ServiceError(400, `Request body ${problem}.`);
162
+ }
163
+ }
164
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,32 @@
1
1
  {
2
2
  "name": "@nihilium/recovery-service",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.9.0",
4
+ "description": "Node-only host for a recovery vault's encrypted records \u2014 value-decomposition storage a provider can run without holding custody (spec \u00a712).",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "node": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsc -b",
21
+ "clean": "tsc -b --clean"
22
+ },
23
+ "dependencies": {
24
+ "@nihilium/recovery-core": "^0.9.0"
25
+ },
26
+ "devDependencies": {
27
+ "@nihilium/recovery-storage-local": "^0.9.0"
28
+ },
29
+ "publishConfig": {
30
+ "access": "public"
31
+ }
32
+ }