@trust-proto/auth-node 0.1.0 → 0.2.1

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/index.cjs ADDED
@@ -0,0 +1,1346 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ COMMAND_ACTIVATED: () => COMMAND_ACTIVATED,
34
+ COMMAND_AUTH: () => COMMAND_AUTH,
35
+ COMMAND_AUTH_DECLINED: () => COMMAND_AUTH_DECLINED,
36
+ COMMAND_AUTH_RESULT: () => COMMAND_AUTH_RESULT,
37
+ COMMAND_AUTH_TIMEOUT: () => COMMAND_AUTH_TIMEOUT,
38
+ COMMAND_ERROR: () => COMMAND_ERROR,
39
+ COMMAND_READY: () => COMMAND_READY,
40
+ ConsoleLogger: () => ConsoleLogger,
41
+ DECLINE_REASON: () => DECLINE_REASON,
42
+ DEFAULT_PORT: () => DEFAULT_PORT,
43
+ LiberionAuth: () => LiberionAuth,
44
+ NoOpLogger: () => NoOpLogger,
45
+ PQCrypto: () => PQCrypto,
46
+ STATUS: () => STATUS,
47
+ USER_CONTRACT_ABI: () => USER_CONTRACT_ABI,
48
+ checkSignature: () => checkSignature,
49
+ clearTokenCache: () => clearTokenCache,
50
+ createWinstonAdapter: () => createWinstonAdapter,
51
+ decryptBuffer: () => decryptBuffer,
52
+ encryptBuffer: () => encryptBuffer,
53
+ extractIpfsHash: () => extractIpfsHash,
54
+ getNetworkConfig: () => getNetworkConfig,
55
+ getTokenFromIPFS: () => getTokenFromIPFS,
56
+ initUserCrypto: () => initUserCrypto,
57
+ shortenAddress: () => shortenAddress
58
+ });
59
+ module.exports = __toCommonJS(index_exports);
60
+
61
+ // src/liberion-auth.ts
62
+ var import_uuid = require("uuid");
63
+ var import_ws2 = __toESM(require("ws"), 1);
64
+ var import_https = __toESM(require("https"), 1);
65
+ var import_http = __toESM(require("http"), 1);
66
+ var import_msgpack2 = require("@msgpack/msgpack");
67
+
68
+ // src/adapters/logger.ts
69
+ var NoOpLogger = class {
70
+ info() {
71
+ }
72
+ warn() {
73
+ }
74
+ error() {
75
+ }
76
+ };
77
+ var ConsoleLogger = class {
78
+ prefix;
79
+ constructor(prefix = "[LiberionAuth]") {
80
+ this.prefix = prefix;
81
+ }
82
+ info(message, meta) {
83
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
84
+ if (meta && Object.keys(meta).length > 0) {
85
+ console.log(`${timestamp} ${this.prefix} INFO: ${message}`, meta);
86
+ } else {
87
+ console.log(`${timestamp} ${this.prefix} INFO: ${message}`);
88
+ }
89
+ }
90
+ warn(message, meta) {
91
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
92
+ if (meta && Object.keys(meta).length > 0) {
93
+ console.warn(`${timestamp} ${this.prefix} WARN: ${message}`, meta);
94
+ } else {
95
+ console.warn(`${timestamp} ${this.prefix} WARN: ${message}`);
96
+ }
97
+ }
98
+ error(message, meta) {
99
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
100
+ if (meta && Object.keys(meta).length > 0) {
101
+ console.error(`${timestamp} ${this.prefix} ERROR: ${message}`, meta);
102
+ } else {
103
+ console.error(`${timestamp} ${this.prefix} ERROR: ${message}`);
104
+ }
105
+ }
106
+ };
107
+ function createWinstonAdapter(winston) {
108
+ return {
109
+ info: (msg, meta) => winston.info(msg, meta),
110
+ warn: (msg, meta) => winston.warn(msg, meta),
111
+ error: (msg, meta) => winston.error(msg, meta)
112
+ };
113
+ }
114
+ function shortenAddress(address) {
115
+ if (!address || address.length < 10) return address ?? "";
116
+ return `${address.slice(0, 6)}...${address.slice(-4)}`;
117
+ }
118
+
119
+ // src/crypto/aes.ts
120
+ var import_crypto = __toESM(require("crypto"), 1);
121
+ var AES_CIPHER = "aes-256-cbc";
122
+ function encryptBuffer(buffer, key) {
123
+ const hashedKey = import_crypto.default.createHash("sha256").update(key).digest("hex").substring(0, 32);
124
+ const iv = import_crypto.default.randomBytes(16);
125
+ const cipher = import_crypto.default.createCipheriv(
126
+ AES_CIPHER,
127
+ Buffer.from(hashedKey),
128
+ iv
129
+ );
130
+ const encrypted = Buffer.concat([cipher.update(buffer), cipher.final()]);
131
+ return Buffer.concat([iv, encrypted]);
132
+ }
133
+ function decryptBuffer(data, key) {
134
+ const hashedKey = import_crypto.default.createHash("sha256").update(key).digest("hex").substring(0, 32);
135
+ const iv = data.subarray(0, 16);
136
+ const encryptedData = data.subarray(16);
137
+ const decipher = import_crypto.default.createDecipheriv(AES_CIPHER, hashedKey, iv);
138
+ return Buffer.concat([decipher.update(encryptedData), decipher.final()]);
139
+ }
140
+
141
+ // src/crypto/signature.ts
142
+ var import_ml_dsa = require("@noble/post-quantum/ml-dsa.js");
143
+ function normalizeToBytes(data) {
144
+ if (data instanceof Uint8Array) {
145
+ return data;
146
+ }
147
+ if (Buffer.isBuffer(data)) {
148
+ return new Uint8Array(data);
149
+ }
150
+ if (data instanceof ArrayBuffer) {
151
+ return new Uint8Array(data);
152
+ }
153
+ if (typeof data === "string") {
154
+ if (data.length % 2 === 0 && /^[0-9a-fA-F]*$/.test(data)) {
155
+ return new Uint8Array(Buffer.from(data, "hex"));
156
+ }
157
+ return new Uint8Array(Buffer.from(data, "utf8"));
158
+ }
159
+ throw new Error(`Invalid data type: ${typeof data}`);
160
+ }
161
+ function normalizeSignature(signature) {
162
+ if (signature instanceof Uint8Array) {
163
+ return signature;
164
+ }
165
+ if (Buffer.isBuffer(signature)) {
166
+ return new Uint8Array(signature);
167
+ }
168
+ if (signature instanceof ArrayBuffer) {
169
+ return new Uint8Array(signature);
170
+ }
171
+ if (typeof signature === "string") {
172
+ return new Uint8Array(Buffer.from(signature, "hex"));
173
+ }
174
+ throw new Error(`Invalid signature format: ${typeof signature}`);
175
+ }
176
+ function initUserCrypto(userToken, logger = new NoOpLogger()) {
177
+ logger.info("[initUserCrypto] Initializing user crypto with ML-DSA-87");
178
+ if (!userToken || !userToken.publicKeySign) {
179
+ throw new Error("Invalid user token: publicKeySign not found");
180
+ }
181
+ const dsaPublicKey = new Uint8Array(
182
+ Buffer.from(userToken.publicKeySign, "base64")
183
+ );
184
+ if (dsaPublicKey.length !== 2592) {
185
+ throw new Error(
186
+ `Invalid ML-DSA-87 public key size: expected 2592 bytes, got ${dsaPublicKey.length}`
187
+ );
188
+ }
189
+ let kemPublicKey = null;
190
+ if (userToken.publicKeyEncrypt) {
191
+ try {
192
+ const kemBuffer = Buffer.from(userToken.publicKeyEncrypt, "base64");
193
+ if (kemBuffer.length !== 1568) {
194
+ logger.warn(
195
+ `[initUserCrypto] Invalid ML-KEM-1024 public key size: expected 1568, got ${kemBuffer.length}`
196
+ );
197
+ } else {
198
+ kemPublicKey = new Uint8Array(kemBuffer);
199
+ }
200
+ } catch (ex) {
201
+ const error = ex;
202
+ logger.warn(
203
+ `[initUserCrypto] Failed to parse publicKeyEncrypt: ${error.message}`
204
+ );
205
+ }
206
+ }
207
+ const verifySignature = (msg, sig) => {
208
+ try {
209
+ const msgBytes = normalizeToBytes(msg);
210
+ const sigBytes = normalizeSignature(sig);
211
+ const isValid = import_ml_dsa.ml_dsa87.verify(sigBytes, msgBytes, dsaPublicKey);
212
+ logger.info("[initUserCrypto.verifySignature] Verification result", {
213
+ isValid,
214
+ msgLength: msgBytes.length,
215
+ sigLength: sigBytes.length
216
+ });
217
+ return isValid;
218
+ } catch (ex) {
219
+ const error = ex;
220
+ logger.error(
221
+ `[initUserCrypto.verifySignature] Error: ${error.message}`
222
+ );
223
+ return false;
224
+ }
225
+ };
226
+ logger.info("[initUserCrypto] Crypto initialized successfully", {
227
+ algorithm: "ML-DSA-87 + ML-KEM-1024",
228
+ dsaPublicKeySize: dsaPublicKey.length,
229
+ kemPublicKeySize: kemPublicKey?.length ?? 0,
230
+ hasEncryption: !!kemPublicKey
231
+ });
232
+ return {
233
+ dsaPublicKey,
234
+ kemPublicKey,
235
+ algorithm: "ML-DSA-87",
236
+ userToken,
237
+ verifySignature
238
+ };
239
+ }
240
+ function checkSignature(crypto2, data, signature, logger = new NoOpLogger()) {
241
+ logger.info("[checkSignature] Checking ML-DSA-87 signature");
242
+ if (!crypto2 || !crypto2.verifySignature) {
243
+ throw new Error("Invalid crypto instance: missing verifySignature method");
244
+ }
245
+ if (!data || !signature) {
246
+ throw new Error("Missing data or signature");
247
+ }
248
+ const msgBytes = normalizeToBytes(data);
249
+ const sigBytes = normalizeSignature(signature);
250
+ logger.info("[checkSignature] Data and signature normalized", {
251
+ dataLength: msgBytes.length,
252
+ signatureLength: sigBytes.length,
253
+ algorithm: crypto2.algorithm
254
+ });
255
+ if (sigBytes.length < 4500 || sigBytes.length > 4700) {
256
+ logger.warn("[checkSignature] Unexpected signature size", {
257
+ expected: "~4595 bytes (ML-DSA-87)",
258
+ actual: sigBytes.length
259
+ });
260
+ }
261
+ const isValid = crypto2.verifySignature(msgBytes, sigBytes);
262
+ if (!isValid) {
263
+ throw new Error("Invalid signature");
264
+ }
265
+ logger.info("[checkSignature] ML-DSA-87 signature verified successfully");
266
+ return true;
267
+ }
268
+
269
+ // src/blockchain/token-provider.ts
270
+ var import_ethers = require("ethers");
271
+
272
+ // src/blockchain/constants.ts
273
+ var PRODUCTION_CONFIG = {
274
+ RPC_ENDPOINT: "https://bc.liberion.com/rpc",
275
+ USER_CONTRACT_ADDRESS: "0xB0b23F79Bf023C97f0235BC0ffC5a8258C03fef9",
276
+ IPFS_GATEWAY: "https://secure.liberion.com",
277
+ TRUST_GATE_URL: "wss://gate.liberion.com"
278
+ };
279
+ var DEVELOPMENT_CONFIG = {
280
+ RPC_ENDPOINT: "https://bc.liberion.dev/rpc",
281
+ USER_CONTRACT_ADDRESS: "0xb584861f5760E9B04B7439c777246c5B82FE6Fff",
282
+ IPFS_GATEWAY: "https://secure.liberion.dev",
283
+ TRUST_GATE_URL: "wss://gate.liberion.dev"
284
+ };
285
+ function getNetworkConfig(env = "production") {
286
+ return env === "development" ? DEVELOPMENT_CONFIG : PRODUCTION_CONFIG;
287
+ }
288
+ var USER_CONTRACT_ABI = [
289
+ {
290
+ inputs: [
291
+ {
292
+ internalType: "address",
293
+ name: "tokenId",
294
+ type: "address"
295
+ }
296
+ ],
297
+ name: "tokenURI",
298
+ outputs: [
299
+ {
300
+ internalType: "string",
301
+ name: "",
302
+ type: "string"
303
+ }
304
+ ],
305
+ stateMutability: "view",
306
+ type: "function"
307
+ }
308
+ ];
309
+
310
+ // src/blockchain/token-provider.ts
311
+ var tokenCache = /* @__PURE__ */ new Map();
312
+ var CACHE_TTL = 15 * 60 * 1e3;
313
+ function extractIpfsHash(tokenURI) {
314
+ if (!tokenURI) {
315
+ throw new Error("tokenURI is empty");
316
+ }
317
+ if (tokenURI.startsWith("ipfs://")) {
318
+ return tokenURI.replace("ipfs://", "");
319
+ }
320
+ if (tokenURI.includes("/ipfs/")) {
321
+ const parts = tokenURI.split("/ipfs/");
322
+ return parts[1];
323
+ }
324
+ if (tokenURI.includes("liberion.com/")) {
325
+ const parts = tokenURI.split("/");
326
+ return parts[parts.length - 1];
327
+ }
328
+ return tokenURI;
329
+ }
330
+ async function fetchFromIPFS(ipfsHash, ipfsGateway, logger) {
331
+ const cached = tokenCache.get(ipfsHash);
332
+ if (cached && cached.expiry > Date.now()) {
333
+ logger.info("[fetchFromIPFS] Cache hit", { ipfsHash });
334
+ return cached.data;
335
+ }
336
+ const url = `${ipfsGateway}/${ipfsHash}`;
337
+ logger.info("[fetchFromIPFS] Fetching from IPFS", { url });
338
+ const response = await fetch(url, {
339
+ headers: {
340
+ Accept: "application/json"
341
+ }
342
+ });
343
+ if (!response.ok) {
344
+ throw new Error(`IPFS fetch failed: ${response.status} ${response.statusText}`);
345
+ }
346
+ const data = await response.json();
347
+ tokenCache.set(ipfsHash, {
348
+ data,
349
+ expiry: Date.now() + CACHE_TTL
350
+ });
351
+ logger.info("[fetchFromIPFS] Token fetched and cached", { ipfsHash });
352
+ return data;
353
+ }
354
+ async function getTokenFromIPFS(address, logger = new NoOpLogger(), networkConfig) {
355
+ const config = networkConfig ?? getNetworkConfig();
356
+ logger.info("[getTokenFromIPFS] Getting SNFT token from IPFS", { address: shortenAddress(address) });
357
+ const provider = new import_ethers.JsonRpcProvider(config.RPC_ENDPOINT);
358
+ const contract = new import_ethers.Contract(
359
+ config.USER_CONTRACT_ADDRESS,
360
+ USER_CONTRACT_ABI,
361
+ provider
362
+ );
363
+ const tokenURI = await contract.tokenURI(address);
364
+ logger.info("[getTokenFromIPFS] Token URI retrieved", {
365
+ address: shortenAddress(address),
366
+ tokenURI
367
+ });
368
+ const ipfsHash = extractIpfsHash(tokenURI);
369
+ logger.info("[getTokenFromIPFS] IPFS hash extracted", {
370
+ address: shortenAddress(address),
371
+ ipfsHash
372
+ });
373
+ const userToken = await fetchFromIPFS(ipfsHash, config.IPFS_GATEWAY, logger);
374
+ if (!userToken.publicKeySign || !userToken.publicKeyEncrypt) {
375
+ throw new Error("Invalid SNFT token structure: missing public keys");
376
+ }
377
+ logger.info("[getTokenFromIPFS] SNFT token fetched successfully", {
378
+ address: shortenAddress(address),
379
+ hasPublicKeySign: !!userToken.publicKeySign,
380
+ hasPublicKeyEncrypt: !!userToken.publicKeyEncrypt,
381
+ hasAssets: !!userToken.assets
382
+ });
383
+ return userToken;
384
+ }
385
+ function clearTokenCache() {
386
+ tokenCache.clear();
387
+ }
388
+
389
+ // src/protocol/trust-gate-client.ts
390
+ var import_ws = __toESM(require("ws"), 1);
391
+ var import_msgpack = require("@msgpack/msgpack");
392
+ var TrustGateClient = class {
393
+ address;
394
+ timeout;
395
+ ws = null;
396
+ pendingRequests = /* @__PURE__ */ new Map();
397
+ requestId = 0;
398
+ connectionTimeout = null;
399
+ logger;
400
+ constructor(options) {
401
+ this.address = options.address;
402
+ this.timeout = options.timeout ?? 1e4;
403
+ this.logger = options.logger ?? new NoOpLogger();
404
+ }
405
+ /**
406
+ * Opens WebSocket connection
407
+ */
408
+ async open() {
409
+ this.logger.info(`[TrustGateClient] Attempting to connect to ${this.address}`);
410
+ return new Promise((resolve, reject) => {
411
+ try {
412
+ this.ws = new import_ws.default(this.address);
413
+ this.ws.on("open", () => {
414
+ if (this.connectionTimeout) {
415
+ clearTimeout(this.connectionTimeout);
416
+ }
417
+ this.logger.info(`[TrustGateClient] Connected to ${this.address}`);
418
+ resolve();
419
+ });
420
+ this.ws.on("message", (data) => {
421
+ try {
422
+ const message = (0, import_msgpack.decode)(data);
423
+ this.handleMessage(message);
424
+ } catch (ex) {
425
+ const error = ex;
426
+ this.logger.error(
427
+ `[TrustGateClient] Failed to decode message: ${error.message}`
428
+ );
429
+ }
430
+ });
431
+ this.ws.on("close", (code) => {
432
+ this.logger.info(
433
+ `[TrustGateClient] Connection closed with code ${code}`
434
+ );
435
+ this.pendingRequests.forEach((request, id) => {
436
+ request.reject(new Error(`Connection closed with code ${code}`));
437
+ this.pendingRequests.delete(id);
438
+ });
439
+ });
440
+ this.ws.on("error", (error) => {
441
+ if (this.connectionTimeout) {
442
+ clearTimeout(this.connectionTimeout);
443
+ }
444
+ this.logger.error("[TrustGateClient] Connection error", {
445
+ message: error.message,
446
+ code: error.code,
447
+ address: this.address
448
+ });
449
+ const enhancedError = new Error(
450
+ `Failed to connect to Trust Gate Server at ${this.address}: ${error.message}`
451
+ );
452
+ enhancedError.code = error.code;
453
+ reject(enhancedError);
454
+ });
455
+ this.connectionTimeout = setTimeout(() => {
456
+ if (this.ws && this.ws.readyState !== import_ws.default.OPEN) {
457
+ this.ws.terminate();
458
+ this.logger.error("[TrustGateClient] Connection timeout", {
459
+ address: this.address,
460
+ timeout: this.timeout
461
+ });
462
+ reject(
463
+ new Error(
464
+ `Connection timeout: Trust Gate Server at ${this.address} did not respond within ${this.timeout}ms`
465
+ )
466
+ );
467
+ }
468
+ }, this.timeout);
469
+ } catch (ex) {
470
+ reject(ex);
471
+ }
472
+ });
473
+ }
474
+ /**
475
+ * Sends message and waits for response
476
+ */
477
+ send(data) {
478
+ if (!this.ws || this.ws.readyState !== import_ws.default.OPEN) {
479
+ return Promise.reject(new Error("WebSocket is not open"));
480
+ }
481
+ return new Promise((resolve, reject) => {
482
+ const requestId = ++this.requestId;
483
+ const timeout = setTimeout(() => {
484
+ this.pendingRequests.delete(requestId);
485
+ reject(new Error("Request timeout"));
486
+ }, 3e4);
487
+ this.pendingRequests.set(requestId, {
488
+ resolve,
489
+ reject,
490
+ timeout
491
+ });
492
+ const message = (0, import_msgpack.encode)({ ...data, _requestId: requestId });
493
+ this.ws.send(message);
494
+ });
495
+ }
496
+ /**
497
+ * Handles incoming message
498
+ */
499
+ handleMessage(message) {
500
+ const requestId = message._requestId;
501
+ if (!requestId) {
502
+ this.logger.warn(
503
+ "[TrustGateClient] Received message without requestId",
504
+ message
505
+ );
506
+ return;
507
+ }
508
+ const pending = this.pendingRequests.get(requestId);
509
+ if (!pending) {
510
+ this.logger.warn(
511
+ `[TrustGateClient] No pending request for id ${requestId}`
512
+ );
513
+ return;
514
+ }
515
+ clearTimeout(pending.timeout);
516
+ this.pendingRequests.delete(requestId);
517
+ if (message._ === "error") {
518
+ pending.reject(
519
+ new Error(message.message || "Unknown error")
520
+ );
521
+ } else {
522
+ pending.resolve(message);
523
+ }
524
+ }
525
+ /**
526
+ * Closes WebSocket connection
527
+ */
528
+ close() {
529
+ if (this.connectionTimeout) {
530
+ clearTimeout(this.connectionTimeout);
531
+ }
532
+ if (this.ws) {
533
+ this.ws.close();
534
+ this.ws = null;
535
+ }
536
+ }
537
+ /**
538
+ * Creates authorization task via Trust Gate Server
539
+ */
540
+ async createTask(params) {
541
+ const response = await this.send({
542
+ _: "task_init",
543
+ projectId: params.projectId,
544
+ scenarioId: params.scenarioId,
545
+ clientKey: params.clientKey
546
+ });
547
+ if (response.status !== "ok") {
548
+ throw new Error(response.message || "Task creation failed");
549
+ }
550
+ return {
551
+ linkWeb: response.linkWeb,
552
+ taskId: response.taskId
553
+ };
554
+ }
555
+ };
556
+
557
+ // src/protocol/constants.ts
558
+ var COMMAND_ACTIVATE = "activate";
559
+ var COMMAND_READY = "ready";
560
+ var COMMAND_AUTH = "auth";
561
+ var COMMAND_AUTH_RESULT = "auth_result";
562
+ var COMMAND_AUTH_INIT = "auth_init";
563
+ var COMMAND_ACTIVATED = "activated";
564
+ var COMMAND_AUTH_DECLINED = "declined";
565
+ var COMMAND_AUTH_TIMEOUT = "timeout";
566
+ var COMMAND_RECONNECT = "reconnect";
567
+ var COMMAND_CONNECTION_FAILED = "connection_failed";
568
+ var COMMAND_ERROR = "error";
569
+ var COMMAND_HEALTH = "health";
570
+ var SOCKET_PING_TIMEOUT = 3e4;
571
+ var AUTHORIZATION_TIME_FRAME = 10 * 60 * 1e3;
572
+ var DEFAULT_PORT = 31313;
573
+ var STATUS = {
574
+ OK: "ok",
575
+ FAILED: "error"
576
+ };
577
+ var DECLINE_REASON = {
578
+ USER: "user_declined",
579
+ TIMEOUT: "timeout",
580
+ ERROR: "error",
581
+ UNKNOWN: "unknown"
582
+ };
583
+
584
+ // src/liberion-auth.ts
585
+ var LiberionAuth = class {
586
+ logger;
587
+ projectId;
588
+ secretCode;
589
+ networkConfig;
590
+ sessions = {};
591
+ interval = null;
592
+ // Callbacks
593
+ onHello;
594
+ onSuccess;
595
+ onDecline;
596
+ // Static command exports for compatibility
597
+ static COMMAND_AUTH = COMMAND_AUTH;
598
+ static COMMAND_READY = COMMAND_READY;
599
+ constructor(config) {
600
+ this.logger = config.logger ?? (config.debug ? new ConsoleLogger() : new NoOpLogger());
601
+ if (!(0, import_uuid.validate)(config.projectId)) {
602
+ this.logger.error(
603
+ "[LiberionAuth] Invalid projectId (should be UUID string)"
604
+ );
605
+ throw new Error("Invalid projectId: must be a valid UUID");
606
+ }
607
+ this.projectId = config.projectId;
608
+ this.secretCode = config.secretCode;
609
+ this.networkConfig = getNetworkConfig(config.environment);
610
+ this.onHello = config.onHello;
611
+ this.onSuccess = config.onSuccess;
612
+ this.onDecline = config.onDecline;
613
+ const port = config.port ?? DEFAULT_PORT;
614
+ const server = config.ssl ? import_https.default.createServer(config.ssl) : import_http.default.createServer();
615
+ this.logger.info(
616
+ `[LiberionAuth] Starting ws${config.ssl ? "s" : ""} server on port ${port}`
617
+ );
618
+ const wsServer = new import_ws2.WebSocketServer({ server });
619
+ server.listen(port);
620
+ wsServer.on("connection", (socket) => {
621
+ const ws = socket;
622
+ this.newClient(ws);
623
+ ws.on("message", (data) => {
624
+ this.request(ws, data);
625
+ });
626
+ ws.on("close", (code) => {
627
+ this.handleClose(ws, code);
628
+ });
629
+ ws.isAlive = true;
630
+ ws.on("pong", () => {
631
+ ws.isAlive = true;
632
+ });
633
+ });
634
+ this.interval = setInterval(() => {
635
+ wsServer.clients.forEach((socket) => {
636
+ const ws = socket;
637
+ if (ws.isAlive === false) {
638
+ return ws.terminate();
639
+ }
640
+ ws.isAlive = false;
641
+ ws.ping();
642
+ });
643
+ }, SOCKET_PING_TIMEOUT);
644
+ wsServer.on("close", () => {
645
+ if (this.interval) {
646
+ clearInterval(this.interval);
647
+ }
648
+ });
649
+ }
650
+ encode(data) {
651
+ return Buffer.from((0, import_msgpack2.encode)(data));
652
+ }
653
+ decode(data) {
654
+ return (0, import_msgpack2.decode)(data);
655
+ }
656
+ async request(client, data) {
657
+ try {
658
+ const response = await this.readMessage(client, data);
659
+ if (response) {
660
+ this.send(client, response);
661
+ }
662
+ } catch (error) {
663
+ const err = error;
664
+ this.logger.error(`[LiberionAuth] Request error: ${err.message}`);
665
+ this.errorResponse(client, err.message);
666
+ this.finalizeSession(client.clientId);
667
+ }
668
+ }
669
+ send(client, data) {
670
+ if (client.readyState === import_ws2.default.OPEN) {
671
+ client.send(this.encode(data));
672
+ }
673
+ }
674
+ async readMessage(client, rawData) {
675
+ let data;
676
+ try {
677
+ data = this.decode(rawData);
678
+ } catch (error) {
679
+ return this.errorResponse(client, "Invalid message format");
680
+ }
681
+ const command = data._;
682
+ switch (command) {
683
+ case COMMAND_AUTH_INIT:
684
+ return this.responseInit(client, data);
685
+ case COMMAND_ACTIVATE:
686
+ return this.responseActivate(
687
+ client,
688
+ data.data,
689
+ data._requestId
690
+ );
691
+ case COMMAND_AUTH:
692
+ return this.responseAuth(client, data);
693
+ case COMMAND_AUTH_DECLINED:
694
+ return this.responseDeclined(client, data);
695
+ case COMMAND_HEALTH:
696
+ return { status: STATUS.OK };
697
+ case COMMAND_RECONNECT:
698
+ return this.responseReconnect(client, data);
699
+ default:
700
+ return this.errorResponse(client, `Unknown command: ${command}`);
701
+ }
702
+ }
703
+ newClient(client) {
704
+ const clientId = (0, import_uuid.v1)();
705
+ const sessionId = (0, import_uuid.v1)();
706
+ this.sessions[clientId] = {
707
+ client,
708
+ sessionId,
709
+ address: null,
710
+ clientSessionId: null,
711
+ isBrowserSession: false
712
+ };
713
+ client.clientId = clientId;
714
+ this.logger.info("[LiberionAuth] New client connected", {
715
+ clientId,
716
+ activeSessions: Object.keys(this.sessions).length
717
+ });
718
+ setTimeout(() => {
719
+ if (this.sessions[clientId]) {
720
+ this.finalizeSession(clientId, true);
721
+ }
722
+ }, AUTHORIZATION_TIME_FRAME);
723
+ }
724
+ handleClose(client, code) {
725
+ const isNormal = code === 1e3 || code === 1001;
726
+ client.isAlive = false;
727
+ const session = this.sessions[client.clientId];
728
+ if (!session) {
729
+ this.logger.info("[LiberionAuth] Socket closed, session already cleaned", {
730
+ clientId: client.clientId,
731
+ code
732
+ });
733
+ return;
734
+ }
735
+ const shouldPreserve = session.isBrowserSession && !session.authResult;
736
+ this.logger.info("[LiberionAuth] WebSocket closed", {
737
+ clientId: client.clientId,
738
+ code,
739
+ isNormal,
740
+ shouldPreserve
741
+ });
742
+ if (shouldPreserve) {
743
+ if (!isNormal) {
744
+ this.logger.warn("[LiberionAuth] Browser abnormal close, session preserved", {
745
+ clientId: client.clientId,
746
+ code
747
+ });
748
+ }
749
+ this.logger.info("[LiberionAuth] Browser session preserved for reconnect", {
750
+ clientId: client.clientId,
751
+ sessionId: session.sessionId
752
+ });
753
+ return;
754
+ }
755
+ if (!isNormal) {
756
+ this.responseFailed(client, { _: COMMAND_CONNECTION_FAILED });
757
+ }
758
+ delete this.sessions[client.clientId];
759
+ }
760
+ async responseInit(client, data) {
761
+ const session = this.sessions[client.clientId];
762
+ this.logger.info("[LiberionAuth] Browser requesting QR code", {
763
+ clientId: client.clientId,
764
+ sessionId: session.sessionId
765
+ });
766
+ const token = encryptBuffer(
767
+ Buffer.from(session.sessionId),
768
+ this.secretCode
769
+ );
770
+ let trustGateClient = null;
771
+ try {
772
+ this.logger.info("[LiberionAuth] Connecting to Trust Gate Server", {
773
+ url: this.networkConfig.TRUST_GATE_URL
774
+ });
775
+ trustGateClient = new TrustGateClient({
776
+ address: this.networkConfig.TRUST_GATE_URL,
777
+ logger: this.logger
778
+ });
779
+ await trustGateClient.open();
780
+ const result = await trustGateClient.createTask({
781
+ projectId: this.projectId,
782
+ clientKey: token.toString("base64")
783
+ });
784
+ this.logger.info("[LiberionAuth] Task created successfully", {
785
+ linkWeb: result.linkWeb,
786
+ taskId: result.taskId
787
+ });
788
+ session.isBrowserSession = true;
789
+ return {
790
+ _: data._,
791
+ linkWeb: result.linkWeb,
792
+ sessionId: session.sessionId
793
+ };
794
+ } catch (ex) {
795
+ const error = ex;
796
+ this.logger.error("[LiberionAuth] responseInit failed", {
797
+ message: error.message
798
+ });
799
+ let userMessage;
800
+ if (error.message.includes("timeout")) {
801
+ userMessage = "Authorization service timeout. Please try again.";
802
+ } else if (error.message.includes("Failed to connect")) {
803
+ userMessage = "Authorization service unavailable. Please try again later.";
804
+ } else {
805
+ userMessage = "Authorization service error. Please contact support.";
806
+ }
807
+ throw new Error(userMessage);
808
+ } finally {
809
+ trustGateClient?.close();
810
+ }
811
+ }
812
+ async responseActivate(client, encryptedData, requestId) {
813
+ const decrypted = decryptBuffer(Buffer.from(encryptedData), this.secretCode);
814
+ const { address, sessionId } = this.decode(decrypted);
815
+ const session = this.findSession("sessionId", sessionId);
816
+ if (!session) {
817
+ this.logger.error("[LiberionAuth] Session not found for activation", {
818
+ address: shortenAddress(address),
819
+ sessionId
820
+ });
821
+ return this.errorResponse(client, "Session not found", requestId);
822
+ }
823
+ if (session.address) {
824
+ this.logger.warn("[LiberionAuth] Session already activated", {
825
+ existingAddress: shortenAddress(session.address),
826
+ newAddress: shortenAddress(address)
827
+ });
828
+ return this.errorResponse(client, "Session already activated", requestId);
829
+ }
830
+ this.logger.info("[LiberionAuth] Activating session", {
831
+ address: shortenAddress(address),
832
+ sessionId
833
+ });
834
+ session.address = address;
835
+ session.clientSessionId = (0, import_uuid.v1)();
836
+ const isRegistered = this.onHello ? await this.onHello(address) : false;
837
+ const responseData = encryptBuffer(
838
+ this.encode({ clientSessionId: session.clientSessionId, isRegistered }),
839
+ this.secretCode
840
+ );
841
+ this.finalResponse(client, {
842
+ _: COMMAND_READY,
843
+ data: responseData,
844
+ _requestId: requestId
845
+ });
846
+ const browserClient = session.client;
847
+ if (browserClient?.readyState === import_ws2.default.OPEN) {
848
+ this.send(browserClient, { _: COMMAND_ACTIVATED });
849
+ this.logger.info("[LiberionAuth] ACTIVATED sent to browser", { address: shortenAddress(address) });
850
+ }
851
+ return void 0;
852
+ }
853
+ async responseAuth(client, data) {
854
+ const { address, payload, signature, fields } = data;
855
+ if (!address || !payload || !signature || !fields) {
856
+ return this.errorResponse(client, "Missing required parameters");
857
+ }
858
+ let session = null;
859
+ try {
860
+ const decryptedPayload = decryptBuffer(
861
+ Buffer.from(payload),
862
+ this.secretCode
863
+ );
864
+ const { clientSessionId } = this.decode(decryptedPayload);
865
+ if (!clientSessionId || !(0, import_uuid.validate)(clientSessionId)) {
866
+ throw new Error("Invalid clientSessionId");
867
+ }
868
+ session = this.findSession("clientSessionId", clientSessionId);
869
+ if (!session) {
870
+ return this.errorResponse(client, "Session not found");
871
+ }
872
+ const userToken = await getTokenFromIPFS(address, this.logger, this.networkConfig);
873
+ const crypto2 = initUserCrypto(userToken, this.logger);
874
+ checkSignature(crypto2, payload, signature, this.logger);
875
+ const result = this.onSuccess ? await this.onSuccess({ address, fields }) : { token: void 0 };
876
+ this.logger.info("[LiberionAuth] Auth successful", {
877
+ address: shortenAddress(address),
878
+ hasToken: !!result.token
879
+ });
880
+ this.finalResponse(client, {
881
+ _: result.error ? STATUS.FAILED : COMMAND_AUTH,
882
+ message: result.error || "welcome"
883
+ });
884
+ if (result.token) {
885
+ session.authResult = { token: result.token };
886
+ }
887
+ const browserClient = session.client;
888
+ if (browserClient?.readyState === import_ws2.default.OPEN) {
889
+ this.finalResponse(browserClient, {
890
+ _: result.error ? STATUS.FAILED : COMMAND_AUTH_RESULT,
891
+ message: result.error || "welcome",
892
+ payload: { token: result.token }
893
+ });
894
+ }
895
+ return void 0;
896
+ } catch (ex) {
897
+ const error = ex;
898
+ this.logger.error(`[LiberionAuth] Auth failed: ${error.message}`);
899
+ if (session) {
900
+ const browserClient = session.client;
901
+ if (browserClient?.readyState === import_ws2.default.OPEN) {
902
+ this.errorResponse(browserClient, error.message);
903
+ }
904
+ }
905
+ return this.errorResponse(client, error.message);
906
+ }
907
+ }
908
+ responseDeclined(client, data) {
909
+ const { payload } = data;
910
+ let clientSessionId;
911
+ let browserSession = null;
912
+ if (payload) {
913
+ try {
914
+ const decryptedPayload = decryptBuffer(
915
+ Buffer.from(payload),
916
+ this.secretCode
917
+ );
918
+ const decoded = this.decode(decryptedPayload);
919
+ clientSessionId = decoded.clientSessionId;
920
+ if (clientSessionId && (0, import_uuid.validate)(clientSessionId)) {
921
+ browserSession = this.findSession("clientSessionId", clientSessionId);
922
+ }
923
+ } catch (ex) {
924
+ this.logger.error("[LiberionAuth] Failed to decrypt decline payload", {
925
+ error: ex.message
926
+ });
927
+ }
928
+ }
929
+ if (browserSession) {
930
+ this.logger.info("[LiberionAuth] Wallet declined, notifying browser", {
931
+ clientSessionId,
932
+ address: shortenAddress(browserSession.address)
933
+ });
934
+ const browserClient = browserSession.client;
935
+ if (browserClient?.readyState === import_ws2.default.OPEN) {
936
+ this.send(browserClient, {
937
+ _: COMMAND_AUTH_DECLINED,
938
+ message: "Authorization declined"
939
+ });
940
+ } else {
941
+ browserSession.declineResult = { message: "Authorization declined" };
942
+ this.logger.info("[LiberionAuth] Browser offline, decline stored", {
943
+ clientSessionId
944
+ });
945
+ }
946
+ if (this.onDecline) {
947
+ this.onDecline({
948
+ address: browserSession.address,
949
+ reason: DECLINE_REASON.USER,
950
+ message: "Authorization declined",
951
+ declinedBy: "wallet",
952
+ sessionId: browserSession.sessionId
953
+ }).catch((ex) => {
954
+ this.logger.error(`[LiberionAuth] onDecline error: ${ex.message}`);
955
+ });
956
+ }
957
+ this.finalResponse(client, {
958
+ _: COMMAND_AUTH_DECLINED,
959
+ message: "Authorization declined"
960
+ });
961
+ return void 0;
962
+ }
963
+ this.logger.error("[LiberionAuth] Browser session not found for decline", {
964
+ clientSessionId,
965
+ hasPayload: !!payload
966
+ });
967
+ return this.responseFailed(client, data);
968
+ }
969
+ responseReconnect(client, data) {
970
+ const sessionId = data.sessionId;
971
+ if (!sessionId) {
972
+ return this.errorResponse(client, "sessionId required");
973
+ }
974
+ const existingSession = this.findSession("sessionId", sessionId);
975
+ if (!existingSession) {
976
+ return this.errorResponse(client, "session_not_found");
977
+ }
978
+ const oldClientId = Object.keys(this.sessions).find(
979
+ (k) => this.sessions[k] === existingSession
980
+ );
981
+ this.logger.info("[LiberionAuth] Reconnecting session", {
982
+ sessionId,
983
+ oldClientId,
984
+ newClientId: client.clientId,
985
+ hasAuthResult: !!existingSession.authResult
986
+ });
987
+ existingSession.client = client;
988
+ if (oldClientId) {
989
+ delete this.sessions[oldClientId];
990
+ }
991
+ this.sessions[client.clientId] = existingSession;
992
+ if (existingSession.authResult) {
993
+ this.send(client, {
994
+ _: COMMAND_AUTH_RESULT,
995
+ message: "welcome",
996
+ payload: existingSession.authResult
997
+ });
998
+ }
999
+ if (existingSession.declineResult) {
1000
+ this.send(client, {
1001
+ _: COMMAND_AUTH_DECLINED,
1002
+ message: existingSession.declineResult.message
1003
+ });
1004
+ this.logger.info("[LiberionAuth] Sent stored decline", { sessionId });
1005
+ }
1006
+ let status;
1007
+ if (existingSession.authResult) {
1008
+ status = "completed";
1009
+ } else if (existingSession.declineResult) {
1010
+ status = "declined";
1011
+ } else if (existingSession.address) {
1012
+ status = "activated";
1013
+ } else {
1014
+ status = "waiting";
1015
+ }
1016
+ return {
1017
+ _: COMMAND_RECONNECT,
1018
+ status
1019
+ };
1020
+ }
1021
+ responseFailed(client, data) {
1022
+ const session = this.sessions[client.clientId];
1023
+ if (!session) {
1024
+ return this.errorResponse(client, "Session not found");
1025
+ }
1026
+ const command = data._;
1027
+ const message = data.message || "Authorization declined";
1028
+ let reason;
1029
+ switch (command) {
1030
+ case COMMAND_AUTH_DECLINED:
1031
+ reason = DECLINE_REASON.USER;
1032
+ break;
1033
+ case COMMAND_AUTH_TIMEOUT:
1034
+ reason = DECLINE_REASON.TIMEOUT;
1035
+ break;
1036
+ case COMMAND_CONNECTION_FAILED:
1037
+ case COMMAND_ERROR:
1038
+ reason = DECLINE_REASON.ERROR;
1039
+ break;
1040
+ default:
1041
+ reason = DECLINE_REASON.UNKNOWN;
1042
+ }
1043
+ const isBrowser = session.client === client && session.isBrowserSession;
1044
+ this.logger.info("[LiberionAuth] Authorization declined", {
1045
+ sessionId: session.sessionId,
1046
+ address: shortenAddress(session.address),
1047
+ reason,
1048
+ declinedBy: isBrowser ? "browser" : "wallet"
1049
+ });
1050
+ if (this.onDecline) {
1051
+ this.onDecline({
1052
+ address: session.address,
1053
+ reason,
1054
+ message,
1055
+ declinedBy: isBrowser ? "browser" : "wallet",
1056
+ sessionId: session.sessionId
1057
+ }).catch((ex) => {
1058
+ this.logger.error(`[LiberionAuth] onDecline error: ${ex.message}`);
1059
+ });
1060
+ }
1061
+ this.finalResponse(client, { _: command, message });
1062
+ return void 0;
1063
+ }
1064
+ errorResponse(client, error, requestId) {
1065
+ this.logger.error("[LiberionAuth] Sending error", {
1066
+ clientId: client.clientId,
1067
+ error
1068
+ });
1069
+ this.finalResponse(client, {
1070
+ _: COMMAND_ERROR,
1071
+ message: error,
1072
+ ...requestId && { _requestId: requestId }
1073
+ });
1074
+ return false;
1075
+ }
1076
+ finalResponse(client, data) {
1077
+ if (client.readyState === import_ws2.default.OPEN) {
1078
+ this.send(client, data);
1079
+ }
1080
+ this.closeClient(client);
1081
+ }
1082
+ finalizeSession(clientId, isTimeout = false) {
1083
+ const session = this.sessions[clientId];
1084
+ if (!session) return;
1085
+ if (isTimeout) {
1086
+ this.responseFailed(session.client, {
1087
+ _: COMMAND_AUTH_TIMEOUT,
1088
+ message: "Authorization timeout"
1089
+ });
1090
+ } else {
1091
+ this.closeClient(session.client);
1092
+ }
1093
+ }
1094
+ closeClient(client) {
1095
+ delete this.sessions[client.clientId];
1096
+ client?.terminate();
1097
+ }
1098
+ findSession(paramName, searchValue) {
1099
+ for (const key of Object.keys(this.sessions)) {
1100
+ if (this.sessions[key][paramName] === searchValue) {
1101
+ return this.sessions[key];
1102
+ }
1103
+ }
1104
+ return null;
1105
+ }
1106
+ };
1107
+
1108
+ // src/crypto/pq-crypto.ts
1109
+ var import_ml_kem = require("@noble/post-quantum/ml-kem.js");
1110
+ var import_ml_dsa2 = require("@noble/post-quantum/ml-dsa.js");
1111
+ var import_crypto2 = require("crypto");
1112
+ var import_libsodium_wrappers = __toESM(require("libsodium-wrappers"), 1);
1113
+ var { subtle } = import_crypto2.webcrypto;
1114
+ var PROTOCOL_AAD = new TextEncoder().encode("PQv1");
1115
+ var PQCrypto = class {
1116
+ logger;
1117
+ // Trust Gate keys (generated from seed)
1118
+ // Note: kemPrivateKey kept for potential future decryption support
1119
+ _kemPrivateKey = null;
1120
+ kemPublicKey = null;
1121
+ dsaPrivateKey = null;
1122
+ dsaPublicKey = null;
1123
+ // Peer (User/Wallet) public keys
1124
+ peerKEMPublicKey = null;
1125
+ peerDSAPublicKey = null;
1126
+ constructor(logger) {
1127
+ this.logger = logger ?? new NoOpLogger();
1128
+ }
1129
+ /**
1130
+ * Generate Trust Gate session keys from cryptographic seed
1131
+ * @param seed - Random seed (at least 96 bytes: 64 for ML-KEM + 32 for ML-DSA)
1132
+ */
1133
+ async generateKeysFromSeed(seed) {
1134
+ try {
1135
+ if (seed.length < 96) {
1136
+ throw new Error(
1137
+ `Seed too short: expected at least 96 bytes (64 for KEM + 32 for DSA), got ${seed.length}`
1138
+ );
1139
+ }
1140
+ const kemSeed = seed.slice(0, 64);
1141
+ const kemKeys = import_ml_kem.ml_kem1024.keygen(kemSeed);
1142
+ this.kemPublicKey = kemKeys.publicKey;
1143
+ this._kemPrivateKey = kemKeys.secretKey;
1144
+ const dsaSeed = seed.slice(64, 96);
1145
+ const dsaKeys = import_ml_dsa2.ml_dsa87.keygen(dsaSeed);
1146
+ this.dsaPublicKey = dsaKeys.publicKey;
1147
+ this.dsaPrivateKey = dsaKeys.secretKey;
1148
+ this.logger.info("[PQCrypto] Keys generated successfully", {
1149
+ kemPublicKeySize: this.kemPublicKey.length,
1150
+ dsaPublicKeySize: this.dsaPublicKey.length
1151
+ });
1152
+ } catch (ex) {
1153
+ const error = ex;
1154
+ this.logger.error(`[PQCrypto] Failed to generate keys: ${error.message}`);
1155
+ throw new Error(`Failed to generate PQ keys: ${error.message}`);
1156
+ }
1157
+ }
1158
+ /**
1159
+ * Import peer's (user) public keys for encryption and verification
1160
+ * @param kemPublicKey - ML-KEM-1024 public key (1568 bytes)
1161
+ * @param dsaPublicKey - ML-DSA-87 public key (2592 bytes)
1162
+ */
1163
+ importPeerPublicKeys(kemPublicKey, dsaPublicKey) {
1164
+ try {
1165
+ this.peerKEMPublicKey = new Uint8Array(kemPublicKey);
1166
+ this.peerDSAPublicKey = new Uint8Array(dsaPublicKey);
1167
+ if (this.peerKEMPublicKey.length !== 1568) {
1168
+ throw new Error(
1169
+ `Invalid ML-KEM-1024 public key size: expected 1568, got ${this.peerKEMPublicKey.length}`
1170
+ );
1171
+ }
1172
+ if (this.peerDSAPublicKey.length !== 2592) {
1173
+ throw new Error(
1174
+ `Invalid ML-DSA-87 public key size: expected 2592, got ${this.peerDSAPublicKey.length}`
1175
+ );
1176
+ }
1177
+ this.logger.info("[PQCrypto] Peer public keys imported", {
1178
+ kemKeySize: this.peerKEMPublicKey.length,
1179
+ dsaKeySize: this.peerDSAPublicKey.length
1180
+ });
1181
+ } catch (ex) {
1182
+ const error = ex;
1183
+ this.logger.error(`[PQCrypto] Failed to import peer keys: ${error.message}`);
1184
+ throw new Error(`Failed to import peer public keys: ${error.message}`);
1185
+ }
1186
+ }
1187
+ /**
1188
+ * Hybrid encryption: ML-KEM + XChaCha20-Poly1305 + ML-DSA signature
1189
+ * Matches client format: version + header + sigLen + signature + cipherText + nonce + ct
1190
+ * @param message - Data to encrypt
1191
+ * @returns Encrypted package in client-compatible format
1192
+ */
1193
+ async encrypt(message) {
1194
+ try {
1195
+ await import_libsodium_wrappers.default.ready;
1196
+ if (!this.peerKEMPublicKey) {
1197
+ throw new Error("Peer KEM public key not imported");
1198
+ }
1199
+ if (!this.dsaPrivateKey) {
1200
+ throw new Error("DSA private key not generated");
1201
+ }
1202
+ const messageBytes = message instanceof Uint8Array ? message : new Uint8Array(message);
1203
+ this.logger.info("[PQCrypto] Starting hybrid encryption", {
1204
+ messageSize: messageBytes.length
1205
+ });
1206
+ const { cipherText: kemCiphertext, sharedSecret } = import_ml_kem.ml_kem1024.encapsulate(
1207
+ this.peerKEMPublicKey
1208
+ );
1209
+ this.logger.info("[PQCrypto] ML-KEM encapsulation complete", {
1210
+ ciphertextSize: kemCiphertext.length,
1211
+ // 1568 bytes
1212
+ sharedSecretSize: sharedSecret.length
1213
+ // 32 bytes
1214
+ });
1215
+ const sharedSecretBuffer = Uint8Array.from(sharedSecret);
1216
+ const secretKeyRaw = await subtle.deriveBits(
1217
+ {
1218
+ name: "HKDF",
1219
+ hash: "SHA-256",
1220
+ salt: PROTOCOL_AAD,
1221
+ info: PROTOCOL_AAD
1222
+ },
1223
+ await subtle.importKey("raw", sharedSecretBuffer, { name: "HKDF" }, false, [
1224
+ "deriveBits"
1225
+ ]),
1226
+ 256
1227
+ );
1228
+ sharedSecret.fill(0);
1229
+ sharedSecretBuffer.fill(0);
1230
+ const versionBuf = new Uint8Array([1]);
1231
+ const header = new Uint8Array(4);
1232
+ new DataView(header.buffer).setUint32(0, kemCiphertext.length, false);
1233
+ const aad = new Uint8Array(PROTOCOL_AAD.length + 1 + 4);
1234
+ aad.set(PROTOCOL_AAD, 0);
1235
+ aad.set(versionBuf, PROTOCOL_AAD.length);
1236
+ aad.set(header, PROTOCOL_AAD.length + 1);
1237
+ const keyBytes = new Uint8Array(secretKeyRaw);
1238
+ const nonce = import_crypto2.webcrypto.getRandomValues(
1239
+ new Uint8Array(import_libsodium_wrappers.default.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES)
1240
+ );
1241
+ const ct = import_libsodium_wrappers.default.crypto_aead_xchacha20poly1305_ietf_encrypt(
1242
+ messageBytes,
1243
+ aad,
1244
+ null,
1245
+ nonce,
1246
+ keyBytes
1247
+ );
1248
+ this.logger.info("[PQCrypto] XChaCha20-Poly1305 encryption complete", {
1249
+ nonceSize: nonce.length,
1250
+ // 24 bytes
1251
+ encryptedSize: ct.length
1252
+ // message + 16 bytes tag
1253
+ });
1254
+ const signature = import_ml_dsa2.ml_dsa87.sign(kemCiphertext, this.dsaPrivateKey);
1255
+ this.logger.info("[PQCrypto] ML-DSA signature created", {
1256
+ signatureSize: signature.length
1257
+ // ~4627 bytes
1258
+ });
1259
+ const sigLenBuf = new Uint8Array(4);
1260
+ new DataView(sigLenBuf.buffer).setUint32(0, signature.length, false);
1261
+ const result = new Uint8Array(
1262
+ 1 + 4 + 4 + signature.length + kemCiphertext.length + nonce.length + ct.length
1263
+ );
1264
+ let offset = 0;
1265
+ result.set(versionBuf, offset);
1266
+ offset += 1;
1267
+ result.set(header, offset);
1268
+ offset += 4;
1269
+ result.set(sigLenBuf, offset);
1270
+ offset += 4;
1271
+ result.set(signature, offset);
1272
+ offset += signature.length;
1273
+ result.set(kemCiphertext, offset);
1274
+ offset += kemCiphertext.length;
1275
+ result.set(nonce, offset);
1276
+ offset += nonce.length;
1277
+ result.set(ct, offset);
1278
+ keyBytes.fill(0);
1279
+ new Uint8Array(secretKeyRaw).fill(0);
1280
+ this.logger.info("[PQCrypto] Hybrid encryption complete", {
1281
+ totalSize: result.length,
1282
+ breakdown: {
1283
+ version: 1,
1284
+ header: 4,
1285
+ sigLen: 4,
1286
+ signature: signature.length,
1287
+ kemCiphertext: kemCiphertext.length,
1288
+ nonce: nonce.length,
1289
+ ct: ct.length
1290
+ }
1291
+ });
1292
+ return Buffer.from(result);
1293
+ } catch (ex) {
1294
+ const error = ex;
1295
+ this.logger.error(`[PQCrypto] Encryption failed: ${error.message}`);
1296
+ throw new Error(`PQ encryption failed: ${error.message}`);
1297
+ }
1298
+ }
1299
+ /**
1300
+ * Check if keys have been generated
1301
+ */
1302
+ hasKeys() {
1303
+ return !!(this.kemPublicKey && this.dsaPublicKey && this._kemPrivateKey);
1304
+ }
1305
+ /**
1306
+ * Export Trust Gate public keys (for sending to client)
1307
+ * @returns Object with publicKeyEncrypt and publicKeySign as Buffers
1308
+ */
1309
+ exportKeys() {
1310
+ if (!this.kemPublicKey || !this.dsaPublicKey) {
1311
+ throw new Error("Keys not generated yet");
1312
+ }
1313
+ return {
1314
+ publicKeyEncrypt: Buffer.from(this.kemPublicKey),
1315
+ publicKeySign: Buffer.from(this.dsaPublicKey)
1316
+ };
1317
+ }
1318
+ };
1319
+ // Annotate the CommonJS export names for ESM import in node:
1320
+ 0 && (module.exports = {
1321
+ COMMAND_ACTIVATED,
1322
+ COMMAND_AUTH,
1323
+ COMMAND_AUTH_DECLINED,
1324
+ COMMAND_AUTH_RESULT,
1325
+ COMMAND_AUTH_TIMEOUT,
1326
+ COMMAND_ERROR,
1327
+ COMMAND_READY,
1328
+ ConsoleLogger,
1329
+ DECLINE_REASON,
1330
+ DEFAULT_PORT,
1331
+ LiberionAuth,
1332
+ NoOpLogger,
1333
+ PQCrypto,
1334
+ STATUS,
1335
+ USER_CONTRACT_ABI,
1336
+ checkSignature,
1337
+ clearTokenCache,
1338
+ createWinstonAdapter,
1339
+ decryptBuffer,
1340
+ encryptBuffer,
1341
+ extractIpfsHash,
1342
+ getNetworkConfig,
1343
+ getTokenFromIPFS,
1344
+ initUserCrypto,
1345
+ shortenAddress
1346
+ });