lightnode-sdk 0.4.5 → 0.4.7
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/crypto.d.ts +6 -4
- package/dist/crypto.js +75 -21
- package/dist/index.d.ts +7 -0
- package/dist/index.js +7 -0
- package/dist/inference.js +1 -1
- package/package.json +1 -1
package/dist/crypto.d.ts
CHANGED
|
@@ -9,12 +9,14 @@
|
|
|
9
9
|
* - AES-GCM ciphertext layout: nonce(12) || ciphertext || tag(16).
|
|
10
10
|
* - Encrypted session key layout: ephemeralPub(65) || nonce(12) || ciphertext || tag(16).
|
|
11
11
|
*
|
|
12
|
-
* Uses the Web Crypto API
|
|
13
|
-
* Node
|
|
14
|
-
*
|
|
12
|
+
* Uses the Web Crypto API. Tries `globalThis.crypto` first (real browsers +
|
|
13
|
+
* Node 19+ where it is global), then falls back to `node:crypto`'s
|
|
14
|
+
* `webcrypto` export (Node 18 and StackBlitz's WebContainer, which exposes
|
|
15
|
+
* the node: module but not the global). No hard dependency on Node, and no
|
|
16
|
+
* polyfill in the browser bundle.
|
|
15
17
|
*/
|
|
16
18
|
/** A fresh 32-byte symmetric session key (random, never derived). */
|
|
17
|
-
export declare function generateSessionKey(): Uint8Array
|
|
19
|
+
export declare function generateSessionKey(): Promise<Uint8Array>;
|
|
18
20
|
/** Fresh ECDH P-256 keypair (extractable; we need to export the public key on the wire). */
|
|
19
21
|
export declare function generateEcdhKeyPair(): Promise<CryptoKeyPair>;
|
|
20
22
|
/** Export an ECDH public key to its raw uncompressed P-256 encoding (65 bytes). */
|
package/dist/crypto.js
CHANGED
|
@@ -9,23 +9,75 @@
|
|
|
9
9
|
* - AES-GCM ciphertext layout: nonce(12) || ciphertext || tag(16).
|
|
10
10
|
* - Encrypted session key layout: ephemeralPub(65) || nonce(12) || ciphertext || tag(16).
|
|
11
11
|
*
|
|
12
|
-
* Uses the Web Crypto API
|
|
13
|
-
* Node
|
|
14
|
-
*
|
|
12
|
+
* Uses the Web Crypto API. Tries `globalThis.crypto` first (real browsers +
|
|
13
|
+
* Node 19+ where it is global), then falls back to `node:crypto`'s
|
|
14
|
+
* `webcrypto` export (Node 18 and StackBlitz's WebContainer, which exposes
|
|
15
|
+
* the node: module but not the global). No hard dependency on Node, and no
|
|
16
|
+
* polyfill in the browser bundle.
|
|
15
17
|
*/
|
|
16
18
|
const AES_KEY_BYTES = 32;
|
|
17
19
|
const GCM_NONCE_BYTES = 12;
|
|
18
20
|
const P256_UNCOMPRESSED_KEY_BYTES = 65;
|
|
19
21
|
const SESSION_KEY_BYTES = 32;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
let resolvedCrypto = null;
|
|
23
|
+
let resolvingCrypto = null;
|
|
24
|
+
async function getCrypto() {
|
|
25
|
+
if (resolvedCrypto)
|
|
26
|
+
return resolvedCrypto;
|
|
27
|
+
if (resolvingCrypto)
|
|
28
|
+
return resolvingCrypto;
|
|
29
|
+
resolvingCrypto = (async () => {
|
|
30
|
+
const diag = [];
|
|
31
|
+
const g = globalThis.crypto;
|
|
32
|
+
diag.push(`globalThis.crypto=${g ? "present" : "missing"}`);
|
|
33
|
+
if (g)
|
|
34
|
+
diag.push(`globalThis.crypto.subtle=${g.subtle ? "present" : "missing"}`);
|
|
35
|
+
if (g)
|
|
36
|
+
diag.push(`globalThis.crypto.getRandomValues=${typeof g.getRandomValues}`);
|
|
37
|
+
if (g?.subtle && typeof g.getRandomValues === "function") {
|
|
38
|
+
const provider = { subtle: g.subtle, getRandomValues: g.getRandomValues.bind(g) };
|
|
39
|
+
resolvedCrypto = provider;
|
|
40
|
+
return provider;
|
|
41
|
+
}
|
|
42
|
+
// Node 18 + StackBlitz WebContainer: globalThis.crypto may be missing, but
|
|
43
|
+
// `node:crypto` exposes the same Web Crypto API via `webcrypto`.
|
|
44
|
+
let nodeCryptoError = null;
|
|
45
|
+
try {
|
|
46
|
+
const mod = (await import("node:crypto"));
|
|
47
|
+
diag.push(`node:crypto=imported`);
|
|
48
|
+
const wc = mod.webcrypto;
|
|
49
|
+
diag.push(`node:crypto.webcrypto=${wc ? "present" : "missing"}`);
|
|
50
|
+
if (wc)
|
|
51
|
+
diag.push(`node:crypto.webcrypto.subtle=${wc.subtle ? "present" : "missing"}`);
|
|
52
|
+
if (wc)
|
|
53
|
+
diag.push(`node:crypto.webcrypto.getRandomValues=${typeof wc.getRandomValues}`);
|
|
54
|
+
if (wc?.subtle && typeof wc.getRandomValues === "function") {
|
|
55
|
+
const provider = { subtle: wc.subtle, getRandomValues: wc.getRandomValues.bind(wc) };
|
|
56
|
+
resolvedCrypto = provider;
|
|
57
|
+
return provider;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
nodeCryptoError = err.message;
|
|
62
|
+
diag.push(`node:crypto=import threw: ${nodeCryptoError}`);
|
|
63
|
+
}
|
|
64
|
+
throw new Error("Web Crypto unavailable. The SDK requires either globalThis.crypto (Node 19+ or any modern browser) " +
|
|
65
|
+
"or node:crypto.webcrypto (Node 18, StackBlitz WebContainer). Diagnostic: " +
|
|
66
|
+
diag.join("; "));
|
|
67
|
+
})();
|
|
68
|
+
try {
|
|
69
|
+
return await resolvingCrypto;
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
resolvingCrypto = null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async function subtle() {
|
|
76
|
+
return (await getCrypto()).subtle;
|
|
25
77
|
}
|
|
26
|
-
function randomBytes(n) {
|
|
78
|
+
async function randomBytes(n) {
|
|
27
79
|
const buf = new Uint8Array(n);
|
|
28
|
-
|
|
80
|
+
(await getCrypto()).getRandomValues(buf);
|
|
29
81
|
return buf;
|
|
30
82
|
}
|
|
31
83
|
/** A fresh 32-byte symmetric session key (random, never derived). */
|
|
@@ -33,16 +85,16 @@ export function generateSessionKey() {
|
|
|
33
85
|
return randomBytes(SESSION_KEY_BYTES);
|
|
34
86
|
}
|
|
35
87
|
/** Fresh ECDH P-256 keypair (extractable; we need to export the public key on the wire). */
|
|
36
|
-
export function generateEcdhKeyPair() {
|
|
37
|
-
return subtle().generateKey({ name: "ECDH", namedCurve: "P-256" }, true, ["deriveBits"]);
|
|
88
|
+
export async function generateEcdhKeyPair() {
|
|
89
|
+
return (await subtle()).generateKey({ name: "ECDH", namedCurve: "P-256" }, true, ["deriveBits"]);
|
|
38
90
|
}
|
|
39
91
|
/** Export an ECDH public key to its raw uncompressed P-256 encoding (65 bytes). */
|
|
40
92
|
export async function exportPublicKey(key) {
|
|
41
|
-
return new Uint8Array(await subtle().exportKey("raw", key));
|
|
93
|
+
return new Uint8Array(await (await subtle()).exportKey("raw", key));
|
|
42
94
|
}
|
|
43
95
|
/** Import a raw uncompressed P-256 public key (65 bytes) into a CryptoKey. */
|
|
44
|
-
export function importPublicKey(raw) {
|
|
45
|
-
return subtle().importKey("raw", raw, { name: "ECDH", namedCurve: "P-256" }, true, []);
|
|
96
|
+
export async function importPublicKey(raw) {
|
|
97
|
+
return (await subtle()).importKey("raw", raw, { name: "ECDH", namedCurve: "P-256" }, true, []);
|
|
46
98
|
}
|
|
47
99
|
/**
|
|
48
100
|
* Derive a 32-byte shared secret from a local ECDH private key and a remote
|
|
@@ -50,15 +102,16 @@ export function importPublicKey(raw) {
|
|
|
50
102
|
* protocol's `priv.ECDH(remotePub)` output exactly.
|
|
51
103
|
*/
|
|
52
104
|
export async function deriveSharedSecret(privateKey, remotePublicKey) {
|
|
53
|
-
return new Uint8Array(await subtle().deriveBits({ name: "ECDH", public: remotePublicKey }, privateKey, 256));
|
|
105
|
+
return new Uint8Array(await (await subtle()).deriveBits({ name: "ECDH", public: remotePublicKey }, privateKey, 256));
|
|
54
106
|
}
|
|
55
107
|
/** Encrypt with AES-256-GCM. Output: nonce(12) || ciphertext || tag(16). */
|
|
56
108
|
export async function encrypt(key, plaintext) {
|
|
57
109
|
if (key.length !== AES_KEY_BYTES)
|
|
58
110
|
throw new Error(`AES key must be ${AES_KEY_BYTES} bytes`);
|
|
59
|
-
const
|
|
60
|
-
const
|
|
61
|
-
const
|
|
111
|
+
const s = await subtle();
|
|
112
|
+
const aesKey = await s.importKey("raw", key, "AES-GCM", false, ["encrypt"]);
|
|
113
|
+
const nonce = await randomBytes(GCM_NONCE_BYTES);
|
|
114
|
+
const ctPlusTag = new Uint8Array(await s.encrypt({ name: "AES-GCM", iv: nonce }, aesKey, plaintext));
|
|
62
115
|
const out = new Uint8Array(GCM_NONCE_BYTES + ctPlusTag.byteLength);
|
|
63
116
|
out.set(nonce, 0);
|
|
64
117
|
out.set(ctPlusTag, GCM_NONCE_BYTES);
|
|
@@ -70,10 +123,11 @@ export async function decrypt(key, ciphertext) {
|
|
|
70
123
|
throw new Error(`AES key must be ${AES_KEY_BYTES} bytes`);
|
|
71
124
|
if (ciphertext.length < GCM_NONCE_BYTES + 16)
|
|
72
125
|
throw new Error("ciphertext too short");
|
|
73
|
-
const
|
|
126
|
+
const s = await subtle();
|
|
127
|
+
const aesKey = await s.importKey("raw", key, "AES-GCM", false, ["decrypt"]);
|
|
74
128
|
const nonce = ciphertext.slice(0, GCM_NONCE_BYTES);
|
|
75
129
|
const body = ciphertext.slice(GCM_NONCE_BYTES);
|
|
76
|
-
return new Uint8Array(await
|
|
130
|
+
return new Uint8Array(await s.decrypt({ name: "AES-GCM", iv: nonce }, aesKey, body));
|
|
77
131
|
}
|
|
78
132
|
/**
|
|
79
133
|
* Wrap (encrypt) a 32-byte session key for delivery to a remote party (e.g. the
|
package/dist/index.d.ts
CHANGED
|
@@ -60,6 +60,13 @@ export declare class LightNode {
|
|
|
60
60
|
baseUrl?: string;
|
|
61
61
|
}): GatewayClient;
|
|
62
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Build-time SDK version. Useful for diagnostic prints in examples and apps so
|
|
65
|
+
* the operator can confirm which version of the SDK is loaded at runtime
|
|
66
|
+
* (especially in registry-proxy environments like StackBlitz where lockfiles
|
|
67
|
+
* may pin an older minor than the local install command suggests).
|
|
68
|
+
*/
|
|
69
|
+
export declare const SDK_VERSION = "0.4.7";
|
|
63
70
|
export { NETWORKS, WORKER_REGISTRY, REGISTRY_TOPICS, aggregateModelStats, aggregateWorkerStats, networkAnalytics, modelStatsCsv, workerStatsCsv, workerJobsCsv, fromWei, computeModelId as modelId, estimateJobFee, JOB_REGISTRY_CONSUMER_ABI, consumerGatewayUrl, consumerGatewayHost, GatewayClient, GatewayHttpError, prepareSession, submitPrompt, decryptResponse, generateEcdhKeyPair, crypto, runInference, runInferenceWithKey, StalledWorkerError, OnChainRevertError, RelayTokenTimeoutError, GatewayAuthError, isStalledWorker, };
|
|
64
71
|
export type { BearerSource, GatewayClientOptions, SelectSessionResult, PrepareSessionResult, UploadBlobResult, SessionTokenResult } from "./gateway.js";
|
|
65
72
|
export type { SessionPreparation, RunInferenceArgs, RunInferenceResult, RunInferenceWithKeyArgs } from "./inference.js";
|
package/dist/index.js
CHANGED
|
@@ -90,6 +90,13 @@ export class LightNode {
|
|
|
90
90
|
return new GatewayClient({ network: this.network, ...opts });
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* Build-time SDK version. Useful for diagnostic prints in examples and apps so
|
|
95
|
+
* the operator can confirm which version of the SDK is loaded at runtime
|
|
96
|
+
* (especially in registry-proxy environments like StackBlitz where lockfiles
|
|
97
|
+
* may pin an older minor than the local install command suggests).
|
|
98
|
+
*/
|
|
99
|
+
export const SDK_VERSION = "0.4.7";
|
|
93
100
|
export { NETWORKS, WORKER_REGISTRY, REGISTRY_TOPICS, aggregateModelStats, aggregateWorkerStats, networkAnalytics, modelStatsCsv, workerStatsCsv, workerJobsCsv, fromWei, computeModelId as modelId, estimateJobFee, JOB_REGISTRY_CONSUMER_ABI, consumerGatewayUrl, consumerGatewayHost,
|
|
94
101
|
// v0.3 inference-submit surface (BETA - see README "Submitting inference").
|
|
95
102
|
GatewayClient, GatewayHttpError, prepareSession, submitPrompt, decryptResponse, generateEcdhKeyPair, crypto,
|
package/dist/inference.js
CHANGED
|
@@ -76,7 +76,7 @@ export const JOB_REGISTRY_CONSUMER_ABI = [
|
|
|
76
76
|
export async function prepareSession(gateway, modelTag) {
|
|
77
77
|
const id = modelId(modelTag);
|
|
78
78
|
const selected = await gateway.selectSession(id);
|
|
79
|
-
const sessionKey = generateSessionKey();
|
|
79
|
+
const sessionKey = await generateSessionKey();
|
|
80
80
|
// Workers' pubkeys arrive as base64; disputer's as hex - decodePublicKey
|
|
81
81
|
// accepts either.
|
|
82
82
|
const workerPub = await importPublicKey(decodePublicKey(selected.workerEncryptionKey));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lightnode-sdk",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.7",
|
|
4
4
|
"description": "Read-only TypeScript client for LightChain AI: workers, jobs, models, on-chain registration, and per-model network analytics. Independent, community-built (not an official LightChain package).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|