@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/LICENSE +21 -0
- package/README.md +419 -0
- package/dist/client.d.ts +80 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +779 -0
- package/dist/client.js.map +1 -0
- package/dist/crypto.d.ts +59 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/crypto.js +213 -0
- package/dist/crypto.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/pdf.d.ts +116 -0
- package/dist/pdf.d.ts.map +1 -0
- package/dist/pdf.js +172 -0
- package/dist/pdf.js.map +1 -0
- package/dist/rail.d.ts +18 -0
- package/dist/rail.d.ts.map +1 -0
- package/dist/rail.js +58 -0
- package/dist/rail.js.map +1 -0
- package/dist/types.d.ts +253 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +13 -0
- package/dist/types.js.map +1 -0
- package/package.json +66 -0
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
|
package/dist/pdf.js.map
ADDED
|
@@ -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
|
package/dist/rail.js.map
ADDED
|
@@ -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"}
|
package/dist/types.d.ts
ADDED
|
@@ -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":"AAAA,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,uFAAuF;IACvF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wFAAwF;IACxF,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,UAAU;IACzB;sEACkE;IAClE,GAAG,EAAE,MAAM,CAAC;IACZ,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,kBAAkB;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,sEAAsE;IACtE,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB;AAED;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IAClC,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,sEAAsE;IACtE,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB;AAED,kFAAkF;AAClF,MAAM,WAAW,sBAAsB;IACrC,YAAY,EAAE,MAAM,CAAC;IACrB,oEAAoE;IACpE,IAAI,EAAE,UAAU,CAAC;IACjB,iFAAiF;IACjF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kFAAkF;IAClF,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uDAAuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,sEAAsE;IACtE,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB;AAED,MAAM,WAAW,eAAe;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,sEAAsE;IACtE,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB;AAED,MAAM,WAAW,qBAAqB;IACpC,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAC3C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,sEAAsE;IACtE,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB;AAED,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,eAAe,GAAG,SAAS,CAAC;AAEnE,MAAM,WAAW,kBAAkB,CAAC,CAAC,GAAG,OAAO;IAC7C,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,CAAC,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,OAAO,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,iBAAiB,EAAE,OAAO,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,cAAc,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IACvC,6EAA6E;IAC7E,iBAAiB,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,QAAQ,GAAG,UAAU,CAAC;IACjE;gEAC4D;IAC5D,WAAW,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,QAAQ,GAAG,UAAU,CAAA;KAAE,GAAG,IAAI,CAAC;CACjF;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,SAAS,GAAG,YAAY,CAAC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,OAAO,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;6DACyD;IACzD,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;mEAC+D;IAC/D,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,oFAAoF;IACpF,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kBAAkB,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,QAAQ,GAAG,UAAU,CAAC;IAClE,YAAY,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,QAAQ,GAAG,UAAU,CAAA;KAAE,GAAG,IAAI,CAAC;CACnF;AAED,MAAM,WAAW,uBAAuB;IACtC,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,KAAK,CAAC;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC3D;AAED,MAAM,WAAW,iBAAiB;IAChC,wCAAwC;IACxC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iEAAiE;IACjE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,6CAA6C;IAC7C,KAAK,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,EAAE,OAAO,CAAC;IAClB,mBAAmB,EAAE,OAAO,CAAC;IAC7B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,iBAAiB;IAChC,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,SAAS,EAAE,OAAO,CAAC;IACnB,oBAAoB,EAAE,OAAO,CAAC;IAC9B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,iBAAiB,EAAE,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,mBAAmB;IAClC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,4BAA4B;IAC3C,MAAM,CAAC,EAAE,KAAK,CAAC;QACb,EAAE,EAAE,MAAM,CAAC;QACX,MAAM,EAAE,SAAS,GAAG,YAAY,CAAC;QACjC,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC,CAAC;CACJ;AAED,qBAAa,aAAc,SAAQ,KAAK;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;gBAGzB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAO;CAQxE"}
|
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
|
+
}
|