@keyhalve/node-sdk 0.1.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/dist/pdf.js ADDED
@@ -0,0 +1,172 @@
1
+ /**
2
+ * QR placement helpers (file mode add-on).
3
+ *
4
+ * `createFileIntent` / `createIntent` seal a document and return
5
+ * `{ retrievalId, key }`. To actually verify it, a scannable QR encoding the
6
+ * verify URL must appear ON the document. WHERE that QR goes is the
7
+ * integrator's call — but historically they were on their own to render it
8
+ * and to guess coordinates, which is fiddly and error-prone (PDFs use a
9
+ * bottom-left origin; every screen uses top-left).
10
+ *
11
+ * This module fixes that with one canonical placement contract used by the
12
+ * SDK, the website "Try it" tool, and the docs, so a position you pick once
13
+ * (e.g. in the tool) maps to the exact same spot here.
14
+ *
15
+ * `pdf-lib` and `qrcode` are OPTIONAL peer dependencies — the core
16
+ * `KeyHalveClient` stays zero-dependency. They're loaded lazily, so you only
17
+ * need them installed if you call {@link embedQr}:
18
+ *
19
+ * npm i pdf-lib qrcode
20
+ *
21
+ * The coordinate math ({@link resolveQrRect}) and {@link buildVerifyUrl} are
22
+ * pure and dependency-free — use them directly if you render PDFs with a
23
+ * different library.
24
+ */
25
+ import { KeyHalveError } from "./types.js";
26
+ const UNIT_TO_PT = { pt: 1, mm: 72 / 25.4, in: 72 };
27
+ /**
28
+ * Smallest QR side we consider reliably scannable from a printed page at
29
+ * arm's length (~72pt ≈ 1in ≈ 2.54cm). Below this, {@link embedQr} emits a
30
+ * one-time console warning. Advisory only — not enforced.
31
+ */
32
+ export const MIN_RECOMMENDED_QR_PT = 72;
33
+ /** base64 → base64url. Phone QR scanners and share-sheets mangle `+`, `/`,
34
+ * and `=` inside URL fragments, so keys must be base64url in a QR. Idempotent
35
+ * on already-base64url input; the `/verify` page accepts both. */
36
+ function toBase64Url(b64) {
37
+ return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
38
+ }
39
+ /**
40
+ * Build the canonical verify URL the QR encodes:
41
+ *
42
+ * <baseUrl>/verify/<retrievalId>#key=<base64url(key)>
43
+ *
44
+ * The key is placed in the URL FRAGMENT (`#key=`), which browsers never send
45
+ * to any server — so the decryption share rides along with the scan without
46
+ * ever touching the platform's logs. The key is converted to base64url so phone
47
+ * scanners don't mangle it.
48
+ */
49
+ export function buildVerifyUrl(retrievalId, key, opts = {}) {
50
+ if (!retrievalId) {
51
+ throw new KeyHalveError("invalid_argument", "retrievalId is required");
52
+ }
53
+ if (!key) {
54
+ throw new KeyHalveError("invalid_argument", "key is required");
55
+ }
56
+ const base = (opts.baseUrl ?? "https://verify.keyhalve.com").replace(/\/+$/, "");
57
+ return `${base}/verify/${encodeURIComponent(retrievalId)}#key=${toBase64Url(key)}`;
58
+ }
59
+ /**
60
+ * Convert a canonical {@link QrPlacement} (top-left-friendly, anchor-relative
61
+ * insets, arbitrary units) into pdf-lib's bottom-left-origin point rectangle
62
+ * for a page of the given size.
63
+ *
64
+ * This is the EXACT conversion the website "Try it" tool uses, so coordinates
65
+ * you copy from the tool land in the same place here. Pure and
66
+ * dependency-free.
67
+ */
68
+ export function resolveQrRect(placement, pageWidthPt, pageHeightPt) {
69
+ const unit = UNIT_TO_PT[placement.units ?? "pt"];
70
+ const size = placement.width * unit;
71
+ const insetX = placement.x * unit;
72
+ const insetY = placement.y * unit;
73
+ const anchor = placement.anchor ?? "top-left";
74
+ const leftAnchored = anchor === "top-left" || anchor === "bottom-left";
75
+ const topAnchored = anchor === "top-left" || anchor === "top-right";
76
+ // Horizontal: inset measured from the left or right edge to the QR's left.
77
+ const x = leftAnchored ? insetX : pageWidthPt - insetX - size;
78
+ // Vertical: pdf-lib y is the QR's BOTTOM edge from the page bottom. A top
79
+ // inset measures from the page top down to the QR's top edge.
80
+ const y = topAnchored ? pageHeightPt - insetY - size : insetY;
81
+ return { x, y, size };
82
+ }
83
+ let smallQrWarned = false;
84
+ /**
85
+ * Stamp a scannable verify QR onto an existing PDF and return the new PDF
86
+ * bytes. The input is not mutated.
87
+ *
88
+ * Requires the optional peer deps `pdf-lib` and `qrcode` (`npm i pdf-lib
89
+ * qrcode`); it throws a `missing_dependency` KeyHalveError if either is
90
+ * absent. Works in Node and the browser (both libs are isomorphic).
91
+ *
92
+ * @example
93
+ * const { retrievalId, key } = await client.createFileIntent({ documentType: "invoice", file });
94
+ * const sealed = await embedQr(originalPdfBytes, {
95
+ * retrievalId, key,
96
+ * placement: { anchor: "bottom-right", x: 36, y: 36, width: 90 },
97
+ * });
98
+ */
99
+ export async function embedQr(pdf, opts) {
100
+ if (!(pdf instanceof Uint8Array) || pdf.length === 0) {
101
+ throw new KeyHalveError("invalid_argument", "pdf must be non-empty Uint8Array/Buffer bytes");
102
+ }
103
+ if (!opts?.placement) {
104
+ throw new KeyHalveError("invalid_argument", "placement is required");
105
+ }
106
+ if (!(opts.placement.width > 0)) {
107
+ throw new KeyHalveError("invalid_argument", "placement.width must be > 0");
108
+ }
109
+ const { PDFDocument } = await loadPdfLib();
110
+ const qrcode = await loadQrcode();
111
+ const url = buildVerifyUrl(opts.retrievalId, opts.key, { baseUrl: opts.baseUrl });
112
+ const q = opts.qr ?? {};
113
+ const dataUrl = await qrcode.toDataURL(url, {
114
+ errorCorrectionLevel: q.errorCorrectionLevel ?? "M",
115
+ margin: q.margin ?? 2,
116
+ width: q.renderPx ?? 1024,
117
+ color: { dark: q.darkColor ?? "#0A0F1E", light: q.lightColor ?? "#FFFFFF" },
118
+ });
119
+ const pngBytes = dataUrlToBytes(dataUrl);
120
+ const doc = await PDFDocument.load(pdf);
121
+ const pages = doc.getPages();
122
+ const pageIndex = (opts.placement.page ?? 1) - 1;
123
+ if (pageIndex < 0 || pageIndex >= pages.length) {
124
+ throw new KeyHalveError("invalid_argument", `placement.page ${opts.placement.page ?? 1} is out of range (document has ${pages.length} page(s))`);
125
+ }
126
+ const page = pages[pageIndex];
127
+ const { width, height } = page.getSize();
128
+ const rect = resolveQrRect(opts.placement, width, height);
129
+ if (rect.size < MIN_RECOMMENDED_QR_PT && !smallQrWarned) {
130
+ smallQrWarned = true;
131
+ // eslint-disable-next-line no-console
132
+ console.warn(`[validpay] QR is ${rect.size.toFixed(0)}pt wide — below the ~${MIN_RECOMMENDED_QR_PT}pt ` +
133
+ "(1in) recommended minimum; it may be hard to scan once printed.");
134
+ }
135
+ if (rect.x < 0 || rect.y < 0 || rect.x + rect.size > width || rect.y + rect.size > height) {
136
+ throw new KeyHalveError("invalid_argument", "placement puts the QR (partly) off the page — check x/y/width against the page size");
137
+ }
138
+ const png = await doc.embedPng(pngBytes);
139
+ page.drawImage(png, { x: rect.x, y: rect.y, width: rect.size, height: rect.size });
140
+ return doc.save();
141
+ }
142
+ // ── internals ───────────────────────────────────────────────────────────────
143
+ /** Decode a `data:image/png;base64,...` URL to raw bytes (Node or browser). */
144
+ function dataUrlToBytes(dataUrl) {
145
+ const b64 = dataUrl.replace(/^data:[^,]*,/, "");
146
+ if (typeof Buffer !== "undefined") {
147
+ return new Uint8Array(Buffer.from(b64, "base64"));
148
+ }
149
+ const bin = atob(b64);
150
+ const out = new Uint8Array(bin.length);
151
+ for (let i = 0; i < bin.length; i++)
152
+ out[i] = bin.charCodeAt(i);
153
+ return out;
154
+ }
155
+ async function loadPdfLib() {
156
+ try {
157
+ return (await import("pdf-lib"));
158
+ }
159
+ catch {
160
+ throw new KeyHalveError("missing_dependency", "embedQr requires the optional peer dependency 'pdf-lib'. Install it: npm i pdf-lib");
161
+ }
162
+ }
163
+ async function loadQrcode() {
164
+ try {
165
+ const mod = (await import("qrcode"));
166
+ return (mod.default ?? mod);
167
+ }
168
+ catch {
169
+ throw new KeyHalveError("missing_dependency", "embedQr requires the optional peer dependency 'qrcode'. Install it: npm i qrcode");
170
+ }
171
+ }
172
+ //# sourceMappingURL=pdf.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pdf.js","sourceRoot":"","sources":["../src/pdf.ts"],"names":[],"mappings":"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"}
package/dist/rail.d.ts ADDED
@@ -0,0 +1,18 @@
1
+ /**
2
+ * KeyHalve rail client (verify side). Fetches the blind rail share `B_keyhalve` from
3
+ * the independent rail and verifies the rail's Ed25519 signature against a PINNED
4
+ * public key. Fails closed on any doubt. The caller XOR-combines the verified rail
5
+ * share with the platform share(s) (from the platform API) and ShareA (the QR key).
6
+ *
7
+ * The pinned key is shipped with the SDK, not fetched at runtime — a hijacked rail or
8
+ * DNS path then produces a signature that fails the pinned check.
9
+ */
10
+ /** Default rail base + pinned KMS Ed25519 public key (SPKI DER, base64). */
11
+ export declare const KEYHALVE_RAIL_BASE_URL = "https://rail.keyhalve.com";
12
+ export declare const KEYHALVE_RAIL_PUBLIC_KEY_SPKI_B64 = "MCowBQYDK2VwAyEAngOcqC4hL467C9RyWUh4bAQD3Fohi9zqhY+l65bul6w=";
13
+ /**
14
+ * Fetch and verify `B_keyhalve` for an intent. Throws (fails closed) on unreachable,
15
+ * missing, revoked, malformed, or bad-signature.
16
+ */
17
+ export declare function fetchRailPiece(fetchImpl: typeof fetch, railBaseUrl: string, pinnedSpkiB64: string, intentId: string): Promise<string>;
18
+ //# sourceMappingURL=rail.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rail.d.ts","sourceRoot":"","sources":["../src/rail.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,4EAA4E;AAC5E,eAAO,MAAM,sBAAsB,8BAA8B,CAAC;AAClE,eAAO,MAAM,iCAAiC,iEACkB,CAAC;AAgBjE;;;GAGG;AACH,wBAAsB,cAAc,CAClC,SAAS,EAAE,OAAO,KAAK,EACvB,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,CAAC,CAqCjB"}
package/dist/rail.js ADDED
@@ -0,0 +1,58 @@
1
+ /**
2
+ * KeyHalve rail client (verify side). Fetches the blind rail share `B_keyhalve` from
3
+ * the independent rail and verifies the rail's Ed25519 signature against a PINNED
4
+ * public key. Fails closed on any doubt. The caller XOR-combines the verified rail
5
+ * share with the platform share(s) (from the platform API) and ShareA (the QR key).
6
+ *
7
+ * The pinned key is shipped with the SDK, not fetched at runtime — a hijacked rail or
8
+ * DNS path then produces a signature that fails the pinned check.
9
+ */
10
+ import { createPublicKey, verify } from "node:crypto";
11
+ import { KeyHalveError } from "./types.js";
12
+ /** Default rail base + pinned KMS Ed25519 public key (SPKI DER, base64). */
13
+ export const KEYHALVE_RAIL_BASE_URL = "https://rail.keyhalve.com";
14
+ export const KEYHALVE_RAIL_PUBLIC_KEY_SPKI_B64 = "MCowBQYDK2VwAyEAngOcqC4hL467C9RyWUh4bAQD3Fohi9zqhY+l65bul6w=";
15
+ const HOLDER = "keyhalve";
16
+ function canonicalMessage(intentId, piece) {
17
+ return `keyhalve-rail.v1\n${intentId}\n${HOLDER}\n${piece}`;
18
+ }
19
+ /**
20
+ * Fetch and verify `B_keyhalve` for an intent. Throws (fails closed) on unreachable,
21
+ * missing, revoked, malformed, or bad-signature.
22
+ */
23
+ export async function fetchRailPiece(fetchImpl, railBaseUrl, pinnedSpkiB64, intentId) {
24
+ const base = railBaseUrl.replace(/\/+$/, "");
25
+ let res;
26
+ try {
27
+ res = await fetchImpl(`${base}/v1/piece/${encodeURIComponent(intentId)}`, {
28
+ method: "GET",
29
+ headers: { Accept: "application/json" },
30
+ });
31
+ }
32
+ catch (cause) {
33
+ throw new KeyHalveError("rail_unreachable", "Could not reach the KeyHalve rail", { cause });
34
+ }
35
+ if (res.status === 404)
36
+ throw new KeyHalveError("rail_not_found", "Rail share not found");
37
+ if (res.status === 409)
38
+ throw new KeyHalveError("rail_revoked", "Rail share revoked");
39
+ if (!res.ok)
40
+ throw new KeyHalveError("rail_error", `Rail returned ${res.status}`);
41
+ const json = (await res.json());
42
+ if (json.error)
43
+ throw new KeyHalveError("rail_error", `Rail error: ${json.error}`);
44
+ if (!json.piece || !json.sig || json.holder !== HOLDER) {
45
+ throw new KeyHalveError("rail_malformed", "Malformed rail response");
46
+ }
47
+ const pub = createPublicKey({
48
+ key: Buffer.from(pinnedSpkiB64, "base64"),
49
+ format: "der",
50
+ type: "spki",
51
+ });
52
+ const ok = verify(null, Buffer.from(canonicalMessage(intentId, json.piece), "utf8"), pub, Buffer.from(json.sig, "base64"));
53
+ if (!ok) {
54
+ throw new KeyHalveError("rail_bad_signature", "Rail response failed signature verification");
55
+ }
56
+ return json.piece;
57
+ }
58
+ //# sourceMappingURL=rail.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rail.js","sourceRoot":"","sources":["../src/rail.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C,4EAA4E;AAC5E,MAAM,CAAC,MAAM,sBAAsB,GAAG,2BAA2B,CAAC;AAClE,MAAM,CAAC,MAAM,iCAAiC,GAC5C,8DAA8D,CAAC;AAEjE,MAAM,MAAM,GAAG,UAAU,CAAC;AAE1B,SAAS,gBAAgB,CAAC,QAAgB,EAAE,KAAa;IACvD,OAAO,qBAAqB,QAAQ,KAAK,MAAM,KAAK,KAAK,EAAE,CAAC;AAC9D,CAAC;AAUD;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,SAAuB,EACvB,WAAmB,EACnB,aAAqB,EACrB,QAAgB;IAEhB,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC7C,IAAI,GAAa,CAAC;IAClB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,SAAS,CAAC,GAAG,IAAI,aAAa,kBAAkB,CAAC,QAAQ,CAAC,EAAE,EAAE;YACxE,MAAM,EAAE,KAAK;YACb,OAAO,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE;SACxC,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,aAAa,CAAC,kBAAkB,EAAE,mCAAmC,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;IAC9F,CAAC;IAED,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG;QAAE,MAAM,IAAI,aAAa,CAAC,gBAAgB,EAAE,sBAAsB,CAAC,CAAC;IAC1F,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG;QAAE,MAAM,IAAI,aAAa,CAAC,cAAc,EAAE,oBAAoB,CAAC,CAAC;IACtF,IAAI,CAAC,GAAG,CAAC,EAAE;QAAE,MAAM,IAAI,aAAa,CAAC,YAAY,EAAE,iBAAiB,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;IAElF,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAsB,CAAC;IACrD,IAAI,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,aAAa,CAAC,YAAY,EAAE,eAAe,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;IACnF,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;QACvD,MAAM,IAAI,aAAa,CAAC,gBAAgB,EAAE,yBAAyB,CAAC,CAAC;IACvE,CAAC;IAED,MAAM,GAAG,GAAG,eAAe,CAAC;QAC1B,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,aAAa,EAAE,QAAQ,CAAC;QACzC,MAAM,EAAE,KAAK;QACb,IAAI,EAAE,MAAM;KACb,CAAC,CAAC;IACH,MAAM,EAAE,GAAG,MAAM,CACf,IAAI,EACJ,MAAM,CAAC,IAAI,CAAC,gBAAgB,CAAC,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,EAC3D,GAAG,EACH,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAChC,CAAC;IACF,IAAI,CAAC,EAAE,EAAE,CAAC;QACR,MAAM,IAAI,aAAa,CAAC,oBAAoB,EAAE,6CAA6C,CAAC,CAAC;IAC/F,CAAC;IACD,OAAO,IAAI,CAAC,KAAK,CAAC;AACpB,CAAC"}
@@ -0,0 +1,253 @@
1
+ export interface KeyHalveClientOptions {
2
+ apiKey: string;
3
+ baseUrl?: string;
4
+ timeout?: number;
5
+ fetch?: typeof fetch;
6
+ /** KeyHalve rail base URL (End-Cell verify). Defaults to https://rail.keyhalve.com. */
7
+ railBaseUrl?: string;
8
+ /** Pinned rail Ed25519 public key (SPKI DER, base64). Defaults to the live rail key. */
9
+ railPublicKeySpki?: string;
10
+ }
11
+ /**
12
+ * Platform delegation (Fork B). When you integrate as a platform and seal on
13
+ * behalf of the businesses you serve, declare which business each seal is for.
14
+ * The verifier sees that business as the issuer ("who"), attributed through your
15
+ * platform ("through whom"), at the `delegated` trust rung.
16
+ *
17
+ * the platform stays blind to the document contents — this is identity only.
18
+ */
19
+ export interface OnBehalfOf {
20
+ /** Your OWN id for this business. The dedupe key: same `ref` ⇒ same tracked
21
+ * sub-issuer (its documents and verification counts roll up). */
22
+ ref: string;
23
+ /** The business name shown to verifiers on a scan. */
24
+ name: string;
25
+ }
26
+ export interface CreateIntentParams {
27
+ documentType: string;
28
+ payload: unknown;
29
+ validFrom?: string;
30
+ validUntil?: string;
31
+ /**
32
+ * Split-key protection (Patent C). Default `true` since 0.4.0: the
33
+ * returned `key` is Share A of the AES key and Share B is stored on
34
+ * the platform server — neither alone decrypts. Set `false` for the
35
+ * legacy single-key flow where `key` is the full AES key.
36
+ */
37
+ splitKey?: boolean;
38
+ /** Seal on behalf of one of your businesses (platform delegation). */
39
+ onBehalfOf?: OnBehalfOf;
40
+ }
41
+ /**
42
+ * Params for {@link KeyHalveClient.createEndCellIntent} (End-Cell, CVCP Layer 6B).
43
+ * Generalises split-key to an n-of-n XOR across ShareA (returned as `key`, embed in
44
+ * the QR) + one mandatory piece per server-side holder. The full key never exists on
45
+ * any single party.
46
+ */
47
+ export interface EndCellIntentParams {
48
+ documentType: string;
49
+ payload: unknown;
50
+ validFrom?: string;
51
+ validUntil?: string;
52
+ /**
53
+ * Server-side holders, one mandatory XOR piece each. Defaults to
54
+ * `["keyhalve", "platform"]` (the blind rail + the platform) → a 3-of-3 split with
55
+ * the receiver's ShareA. Provide more to elect additional independent holders.
56
+ */
57
+ holders?: string[];
58
+ /** Seal on behalf of one of your businesses (platform delegation). */
59
+ onBehalfOf?: OnBehalfOf;
60
+ }
61
+ /** Params for {@link KeyHalveClient.createFileIntent} (file mode, Prompt 099). */
62
+ export interface CreateFileIntentParams {
63
+ documentType: string;
64
+ /** Raw file bytes to seal (PDF/image/DOCX/…). Encrypted locally. */
65
+ file: Uint8Array;
66
+ /** Original filename, stored for issuer records. NOT echoed on public verify. */
67
+ fileName?: string;
68
+ /** MIME type, e.g. "application/pdf". Returned on verify for correct download. */
69
+ fileContentType?: string;
70
+ validFrom?: string;
71
+ validUntil?: string;
72
+ /** Split-key protection (Patent C). Default `true`. */
73
+ splitKey?: boolean;
74
+ /** Seal on behalf of one of your businesses (platform delegation). */
75
+ onBehalfOf?: OnBehalfOf;
76
+ }
77
+ export interface BatchIntentItem {
78
+ documentType: string;
79
+ payload: unknown;
80
+ validFrom?: string;
81
+ validUntil?: string;
82
+ /** Seal on behalf of one of your businesses (platform delegation). */
83
+ onBehalfOf?: OnBehalfOf;
84
+ }
85
+ export interface SelectiveIntentParams {
86
+ documentType: string;
87
+ payload: Record<string, unknown>;
88
+ disclosurePolicy: Record<string, string[]>;
89
+ splitKey?: boolean;
90
+ validFrom?: string;
91
+ validUntil?: string;
92
+ /** Seal on behalf of one of your businesses (platform delegation). */
93
+ onBehalfOf?: OnBehalfOf;
94
+ }
95
+ export interface CreateIntentResult {
96
+ retrievalId: string;
97
+ key: string;
98
+ }
99
+ export type TimeLockStatus = "valid" | "not_yet_valid" | "expired";
100
+ export interface VerifyIntentResult<T = unknown> {
101
+ intentId: string;
102
+ payload: T;
103
+ issuer: string;
104
+ issuerVerified: boolean;
105
+ registeredAt: string;
106
+ status: string;
107
+ integrityVerified: boolean;
108
+ validFrom?: string | null;
109
+ validUntil?: string | null;
110
+ timeLockStatus?: TimeLockStatus | null;
111
+ /** Graded trust rung of the issuer: none < delegated < domain < business. */
112
+ verificationLevel?: "none" | "delegated" | "domain" | "business";
113
+ /** Set when `issuer` was sealed on behalf of, via a platform (delegation).
114
+ * Names the vouching platform and its own proven level. */
115
+ delegatedBy?: {
116
+ platform: string;
117
+ platformLevel: "domain" | "business";
118
+ } | null;
119
+ }
120
+ export interface RevocationResult {
121
+ intentId: string;
122
+ status: string;
123
+ revokedAt?: string;
124
+ reinstatedAt?: string;
125
+ }
126
+ export interface RevocationEvent {
127
+ id: string;
128
+ action: "revoked" | "reinstated";
129
+ reason?: string;
130
+ performedAt: string;
131
+ }
132
+ export interface RawIntentResponse {
133
+ intent_id: string;
134
+ encrypted_payload: string | null;
135
+ issuer: string;
136
+ issuer_verified: boolean;
137
+ registered_at: string;
138
+ status: string;
139
+ document_type?: string;
140
+ commitment_hash?: string;
141
+ /** 1 = legacy SHA-256(plaintext), skipped on verify; 2 = SHA-256(ciphertext),
142
+ * enforced. Absent is treated as 1 (Prompt 097 C-1). */
143
+ commitment_version?: number;
144
+ /** 1 = no AAD (legacy); 2 = {document_type, valid_from, valid_until} bound
145
+ * as AES-GCM AAD. Absent is treated as 1 (Prompt 097 M-5). */
146
+ encryption_version?: number;
147
+ valid_from?: string | null;
148
+ valid_until?: string | null;
149
+ selective_disclosure?: boolean;
150
+ encrypted_key_map?: string;
151
+ split_key?: boolean;
152
+ /** End-Cell (CVCP Layer 6B): key split n-of-n XOR across ShareA + server pieces. */
153
+ end_cell?: boolean;
154
+ revocation_reason?: string;
155
+ revoked_at?: string;
156
+ verification_level?: "none" | "delegated" | "domain" | "business";
157
+ delegated_by?: {
158
+ platform: string;
159
+ platform_level: "domain" | "business";
160
+ } | null;
161
+ }
162
+ export interface RawCreateIntentResponse {
163
+ retrieval_id: string;
164
+ status: string;
165
+ }
166
+ export interface RawBatchCreateResponse {
167
+ results: Array<{
168
+ retrieval_id: string;
169
+ status?: string;
170
+ }>;
171
+ }
172
+ export interface ListIntentsParams {
173
+ /** Max results, default 50, max 200. */
174
+ limit?: number;
175
+ offset?: number;
176
+ /** ISO datetime — only intents created at or after this time. */
177
+ since?: string;
178
+ /** ISO datetime — only intents created at or before this time. */
179
+ until?: string;
180
+ status?: "active" | "revoked";
181
+ documentType?: string;
182
+ /** Ordering by createdAt. Default "desc". */
183
+ order?: "asc" | "desc";
184
+ }
185
+ export interface IntentMetadata {
186
+ retrievalId: string;
187
+ documentType: string;
188
+ status: string;
189
+ createdAt: string;
190
+ revokedAt: string | null;
191
+ revocationReason: string | null;
192
+ validFrom: string | null;
193
+ validUntil: string | null;
194
+ commitmentHash: string | null;
195
+ splitKey: boolean;
196
+ selectiveDisclosure: boolean;
197
+ verificationCount: number;
198
+ lastVerifiedAt: string | null;
199
+ }
200
+ export interface ListIntentsResult {
201
+ intents: IntentMetadata[];
202
+ total: number;
203
+ limit: number;
204
+ offset: number;
205
+ }
206
+ export interface RawIntentMetadata {
207
+ retrieval_id: string;
208
+ document_type: string;
209
+ status: string;
210
+ created_at: string;
211
+ revoked_at: string | null;
212
+ revocation_reason: string | null;
213
+ valid_from: string | null;
214
+ valid_until: string | null;
215
+ commitment_hash: string | null;
216
+ split_key: boolean;
217
+ selective_disclosure: boolean;
218
+ verification_count: number;
219
+ last_verified_at: string | null;
220
+ }
221
+ export interface RawListIntentsResponse {
222
+ intents: RawIntentMetadata[];
223
+ total: number;
224
+ limit: number;
225
+ offset: number;
226
+ }
227
+ export interface RawFragmentResponse {
228
+ fragment_b?: string;
229
+ /** End-Cell (CVCP Layer 6B): the per-holder server pieces. */
230
+ end_cell?: boolean;
231
+ holders?: string[];
232
+ pieces?: Record<string, string>;
233
+ error?: string;
234
+ }
235
+ export interface RawRevocationHistoryResponse {
236
+ events?: Array<{
237
+ id: string;
238
+ action: "revoked" | "reinstated";
239
+ reason?: string;
240
+ performed_at: string;
241
+ }>;
242
+ }
243
+ export declare class KeyHalveError extends Error {
244
+ readonly code: string;
245
+ readonly status?: number;
246
+ readonly details?: unknown;
247
+ constructor(code: string, message: string, options?: {
248
+ status?: number;
249
+ details?: unknown;
250
+ cause?: unknown;
251
+ });
252
+ }
253
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,13 @@
1
+ export class KeyHalveError extends Error {
2
+ code;
3
+ status;
4
+ details;
5
+ constructor(code, message, options = {}) {
6
+ super(message, options.cause !== undefined ? { cause: options.cause } : undefined);
7
+ this.name = "KeyHalveError";
8
+ this.code = code;
9
+ this.status = options.status;
10
+ this.details = options.details;
11
+ }
12
+ }
13
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AA+PA,MAAM,OAAO,aAAc,SAAQ,KAAK;IAC7B,IAAI,CAAS;IACb,MAAM,CAAU;IAChB,OAAO,CAAW;IAE3B,YACE,IAAY,EACZ,OAAe,EACf,UAAmE,EAAE;QAErE,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QACnF,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;QAC5B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAC7B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IACjC,CAAC;CACF"}
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@keyhalve/node-sdk",
3
+ "version": "0.1.0",
4
+ "description": "KeyHalve SDK for Node.js — End-Cell blind-rail document sealing & verification: client-side AES-256-GCM, 3-share key split (QR + platform + independent KeyHalve rail), commitment hashing, QR placement. Zero required production dependencies.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "README.md",
17
+ "LICENSE"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsc -p tsconfig.build.json",
21
+ "test": "vitest run",
22
+ "test:watch": "vitest",
23
+ "typecheck": "tsc --noEmit",
24
+ "prepublishOnly": "npm run build"
25
+ },
26
+ "engines": {
27
+ "node": ">=20"
28
+ },
29
+ "keywords": [
30
+ "keyhalve",
31
+ "encryption",
32
+ "aes-256-gcm",
33
+ "document-verification",
34
+ "check-fraud",
35
+ "authentication",
36
+ "selective-disclosure",
37
+ "split-key",
38
+ "blind-escrow"
39
+ ],
40
+ "homepage": "https://github.com/keyhalve/node-sdk",
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "git+https://github.com/keyhalve/node-sdk.git"
44
+ },
45
+ "bugs": {
46
+ "url": "https://github.com/keyhalve/node-sdk/issues"
47
+ },
48
+ "license": "MIT",
49
+ "author": "KeyHalve (Attestura, LLC)",
50
+ "peerDependencies": {
51
+ "pdf-lib": ">=1.17.0",
52
+ "qrcode": ">=1.5.0"
53
+ },
54
+ "peerDependenciesMeta": {
55
+ "pdf-lib": { "optional": true },
56
+ "qrcode": { "optional": true }
57
+ },
58
+ "devDependencies": {
59
+ "@types/node": "^20.11.0",
60
+ "@types/qrcode": "^1.5.5",
61
+ "pdf-lib": "^1.17.1",
62
+ "qrcode": "^1.5.4",
63
+ "typescript": "^5.4.0",
64
+ "vitest": "^1.6.0"
65
+ }
66
+ }