blockintel-gate-sdk 0.3.8 → 0.3.10

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.js CHANGED
@@ -1,6 +1,7 @@
1
- import { createHash, createVerify, createHmac } from 'crypto';
1
+ import { createHash, createVerify, randomBytes, createSign, createHmac } from 'crypto';
2
2
  import { v4 } from 'uuid';
3
- import { SignCommand } from '@aws-sdk/client-kms';
3
+ import { SignCommand, SigningAlgorithmSpec } from '@aws-sdk/client-kms';
4
+ import { createRequire } from 'module';
4
5
 
5
6
  var __defProp = Object.defineProperty;
6
7
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -914,6 +915,12 @@ var MetricsCollector = class {
914
915
  this.circuitBreakerOpenTotal++;
915
916
  this.emitMetrics();
916
917
  }
918
+ /**
919
+ * Record soft-enforce override (app chose to sign despite BLOCK decision)
920
+ */
921
+ recordSoftBlockOverride(decision) {
922
+ this.emitMetrics();
923
+ }
917
924
  /**
918
925
  * Get current metrics snapshot
919
926
  */
@@ -1214,6 +1221,8 @@ var HeartbeatManager = class {
1214
1221
  consecutiveFailures = 0;
1215
1222
  maxBackoffSeconds = 30;
1216
1223
  // Maximum backoff interval
1224
+ /** SignerId used for the in-flight request; used to ignore stale responses after updateSignerId(). */
1225
+ acquiringForSignerId = null;
1217
1226
  constructor(options) {
1218
1227
  this.httpClient = options.httpClient;
1219
1228
  this.tenantId = options.tenantId;
@@ -1307,12 +1316,20 @@ var HeartbeatManager = class {
1307
1316
  return this.getToken() !== null;
1308
1317
  }
1309
1318
  /**
1310
- * Update signer ID (called when signer is known)
1319
+ * Update signer ID (called when signer is known).
1320
+ * Invalidates current token and triggers an immediate heartbeat acquisition so evaluate() can get a matching token.
1311
1321
  */
1312
1322
  updateSignerId(signerId) {
1313
1323
  if (this.signerId !== signerId) {
1314
1324
  this.signerId = signerId;
1315
1325
  this.currentToken = null;
1326
+ if (this.started && this.apiKey) {
1327
+ setImmediate(() => {
1328
+ this.acquireHeartbeat().catch((err) => {
1329
+ console.warn("[HEARTBEAT] Immediate acquire after signerId change failed:", err instanceof Error ? err.message : err);
1330
+ });
1331
+ });
1332
+ }
1316
1333
  }
1317
1334
  }
1318
1335
  /**
@@ -1328,6 +1345,8 @@ var HeartbeatManager = class {
1328
1345
  {}
1329
1346
  );
1330
1347
  }
1348
+ const signerIdAtRequest = this.signerId;
1349
+ this.acquiringForSignerId = signerIdAtRequest;
1331
1350
  try {
1332
1351
  const response = await this.httpClient.request({
1333
1352
  method: "POST",
@@ -1352,18 +1371,20 @@ var HeartbeatManager = class {
1352
1371
  "Invalid heartbeat response: missing token or expiresAt"
1353
1372
  );
1354
1373
  }
1355
- this.currentToken = {
1356
- token,
1357
- expiresAt,
1358
- jti: response.data.jti,
1359
- policyHash: response.data.policyHash
1360
- };
1361
- console.log("[HEARTBEAT] Acquired heartbeat token", {
1362
- expiresAt,
1363
- jti: response.data.jti,
1364
- policyHash: response.data.policyHash?.substring(0, 8) + "..."
1365
- // DO NOT log token value
1366
- });
1374
+ if (this.signerId === signerIdAtRequest) {
1375
+ this.currentToken = {
1376
+ token,
1377
+ expiresAt,
1378
+ jti: response.data.jti,
1379
+ policyHash: response.data.policyHash
1380
+ };
1381
+ console.log("[HEARTBEAT] Acquired heartbeat token", {
1382
+ expiresAt,
1383
+ jti: response.data.jti,
1384
+ policyHash: response.data.policyHash?.substring(0, 8) + "..."
1385
+ // DO NOT log token value
1386
+ });
1387
+ }
1367
1388
  } else {
1368
1389
  const error = response.error || {};
1369
1390
  throw new GateError(
@@ -1374,6 +1395,10 @@ var HeartbeatManager = class {
1374
1395
  } catch (error) {
1375
1396
  console.error("[HEARTBEAT] Failed to acquire heartbeat:", error.message || error);
1376
1397
  throw error;
1398
+ } finally {
1399
+ if (this.acquiringForSignerId === signerIdAtRequest) {
1400
+ this.acquiringForSignerId = null;
1401
+ }
1377
1402
  }
1378
1403
  }
1379
1404
  /**
@@ -1774,6 +1799,7 @@ var GateClient = class {
1774
1799
  const timestampMs = req.timestampMs ?? nowMs();
1775
1800
  const startTime = Date.now();
1776
1801
  const failSafeMode = this.config.failSafeMode ?? "ALLOW_ON_TIMEOUT";
1802
+ const evaluationMode = this.config.evaluationMode ?? "BLOCKING";
1777
1803
  const requestMode = req.mode || this.mode;
1778
1804
  const requireToken = this.getRequireDecisionToken();
1779
1805
  const executeRequest = async () => {
@@ -2032,6 +2058,20 @@ var GateClient = class {
2032
2058
  }
2033
2059
  }
2034
2060
  if (result.decision === "BLOCK") {
2061
+ if (requestMode === "SOFT_ENFORCE") {
2062
+ console.warn("[SOFT ENFORCE] Policy violation detected - app can override", {
2063
+ requestId,
2064
+ reasonCodes: result.reasonCodes
2065
+ });
2066
+ this.metrics.recordRequest("BLOCK", latencyMs);
2067
+ return {
2068
+ ...result,
2069
+ decision: "BLOCK",
2070
+ enforced: false,
2071
+ mode: "SOFT_ENFORCE",
2072
+ warning: "Policy violation detected. Override at your own risk."
2073
+ };
2074
+ }
2035
2075
  if (requestMode === "SHADOW") {
2036
2076
  console.warn("[GATE SHADOW MODE] Would have blocked transaction", {
2037
2077
  requestId,
@@ -2070,6 +2110,26 @@ var GateClient = class {
2070
2110
  this.metrics.recordRequest("ALLOW", latencyMs);
2071
2111
  return result;
2072
2112
  };
2113
+ if (evaluationMode === "FIRE_AND_FORGET") {
2114
+ executeRequest().then((res) => {
2115
+ if (res.decision === "BLOCK" || res.shadowWouldBlock) {
2116
+ console.warn("[FIRE-AND-FORGET] Would have blocked:", res.reasonCodes);
2117
+ }
2118
+ this.metrics.recordRequest(res.decision === "ALLOW" ? "ALLOW" : "WOULD_BLOCK", Date.now() - startTime);
2119
+ }).catch((err) => {
2120
+ console.error("[FIRE-AND-FORGET] Attestation failed:", err);
2121
+ this.metrics.recordError();
2122
+ });
2123
+ return {
2124
+ decision: "ALLOW",
2125
+ decisionId: requestId,
2126
+ correlationId: requestId,
2127
+ reasonCodes: [],
2128
+ enforced: false,
2129
+ mode: requestMode,
2130
+ fireAndForget: true
2131
+ };
2132
+ }
2073
2133
  try {
2074
2134
  if (this.circuitBreaker) {
2075
2135
  return await this.circuitBreaker.execute(executeRequest);
@@ -2210,6 +2270,102 @@ var GateClient = class {
2210
2270
  intervalMs: args.intervalMs ?? this.config.stepUp?.pollingIntervalMs
2211
2271
  });
2212
2272
  }
2273
+ /**
2274
+ * Evaluate policy and sign in one call when decision is ALLOW.
2275
+ * Convenience for: evaluate → if ALLOW then sign → return { decision, signature }.
2276
+ */
2277
+ async evaluateAndSign(params) {
2278
+ const decision = await this.evaluate({
2279
+ txIntent: params.txIntent,
2280
+ signingContext: params.signingContext
2281
+ });
2282
+ if (decision.decision === "ALLOW") {
2283
+ const signature = await params.signer.sign({
2284
+ keyId: params.keyId,
2285
+ message: params.message,
2286
+ algorithm: params.algorithm ?? "ECDSA_SHA_256"
2287
+ });
2288
+ return { decision, signature };
2289
+ }
2290
+ return { decision };
2291
+ }
2292
+ /**
2293
+ * Attest a completed signature (post-sign). Use when you want zero latency impact on signing
2294
+ * but still want an audit trail. Policy is evaluated against txIntent; returns ALLOW or
2295
+ * POLICY_VIOLATION_DETECTED. Cannot be used for enforcement (signature already created).
2296
+ */
2297
+ async attestCompleted(req) {
2298
+ const requestId = v4();
2299
+ const timestampMs = nowMs();
2300
+ const txIntent = { ...req.txIntent };
2301
+ if (txIntent.to && !txIntent.toAddress) {
2302
+ txIntent.toAddress = txIntent.to;
2303
+ delete txIntent.to;
2304
+ }
2305
+ if (!txIntent.networkFamily && txIntent.chainId) txIntent.networkFamily = "EVM";
2306
+ const signingContext = {
2307
+ ...req.signingContext,
2308
+ signerId: req.signingContext?.signerId ?? req.signature.signerId
2309
+ };
2310
+ const body = {
2311
+ tenantId: this.config.tenantId,
2312
+ requestId,
2313
+ timestampMs,
2314
+ txIntent,
2315
+ signature: req.signature,
2316
+ signingContext
2317
+ };
2318
+ let headers = { "Content-Type": "application/json" };
2319
+ if (this.config.local) ; else if (this.hmacSigner) {
2320
+ const { canonicalizeJson: canonicalizeJson2 } = await Promise.resolve().then(() => (init_canonicalJson(), canonicalJson_exports));
2321
+ const canonicalBodyJson = canonicalizeJson2(body);
2322
+ const hmacHeaders = await this.hmacSigner.signRequest({
2323
+ method: "POST",
2324
+ path: "/defense/attest-completed",
2325
+ tenantId: this.config.tenantId,
2326
+ timestampMs,
2327
+ requestId,
2328
+ body
2329
+ });
2330
+ headers = { ...hmacHeaders };
2331
+ body.__canonicalJson = canonicalBodyJson;
2332
+ } else if (this.apiKeyAuth) {
2333
+ const apiKeyHeaders = this.apiKeyAuth.createHeaders({
2334
+ tenantId: this.config.tenantId,
2335
+ timestampMs,
2336
+ requestId
2337
+ });
2338
+ headers = { ...apiKeyHeaders };
2339
+ } else {
2340
+ throw new Error("No authentication configured");
2341
+ }
2342
+ const apiResponse = await this.httpClient.request({
2343
+ method: "POST",
2344
+ path: "/defense/attest-completed",
2345
+ headers,
2346
+ body,
2347
+ requestId
2348
+ });
2349
+ if (apiResponse.success === true && apiResponse.data) {
2350
+ const data = apiResponse.data;
2351
+ if (data.decision === "POLICY_VIOLATION_DETECTED") {
2352
+ console.warn("[POST-SIGN ATTESTATION] Policy violation detected after signing", {
2353
+ requestId,
2354
+ reasonCodes: data.reasonCodes
2355
+ });
2356
+ }
2357
+ return data;
2358
+ }
2359
+ if (apiResponse.error) {
2360
+ const err = apiResponse.error;
2361
+ throw new GateError(err.code || "SERVER_ERROR", err.message || "Request failed", {
2362
+ status: err.status,
2363
+ correlationId: err.correlationId,
2364
+ requestId
2365
+ });
2366
+ }
2367
+ throw new GateError("INVALID_RESPONSE" /* INVALID_RESPONSE */, "Invalid response from attest-completed", { requestId });
2368
+ }
2213
2369
  /**
2214
2370
  * Wrap AWS SDK v3 KMS client to intercept SignCommand calls
2215
2371
  *
@@ -2242,6 +2398,39 @@ var Gate = class {
2242
2398
  constructor(opts) {
2243
2399
  this.apiKey = opts?.apiKey ?? process.env.BLOCKINTEL_API_KEY;
2244
2400
  }
2401
+ /**
2402
+ * Create a GateClient from environment variables (5-line integration).
2403
+ *
2404
+ * Reads: GATE_BASE_URL, GATE_TENANT_ID, GATE_API_KEY (or GATE_KEY_ID + GATE_HMAC_SECRET), GATE_MODE.
2405
+ */
2406
+ static fromEnv(overrides) {
2407
+ const baseUrl = process.env.GATE_BASE_URL;
2408
+ const tenantId = process.env.GATE_TENANT_ID;
2409
+ const apiKey = process.env.GATE_API_KEY;
2410
+ const keyId = process.env.GATE_KEY_ID;
2411
+ const hmacSecret = process.env.GATE_HMAC_SECRET;
2412
+ const mode = process.env.GATE_MODE ?? "SHADOW";
2413
+ if (!baseUrl || !tenantId) {
2414
+ throw new Error("GATE_BASE_URL and GATE_TENANT_ID environment variables are required");
2415
+ }
2416
+ let auth;
2417
+ if (apiKey) {
2418
+ auth = { mode: "apiKey", apiKey };
2419
+ } else if (keyId && hmacSecret) {
2420
+ auth = { mode: "hmac", keyId, secret: hmacSecret };
2421
+ } else {
2422
+ throw new Error(
2423
+ "Either GATE_API_KEY or (GATE_KEY_ID and GATE_HMAC_SECRET) environment variables are required"
2424
+ );
2425
+ }
2426
+ return new GateClient({
2427
+ baseUrl,
2428
+ tenantId,
2429
+ auth,
2430
+ mode,
2431
+ ...overrides
2432
+ });
2433
+ }
2245
2434
  /**
2246
2435
  * Guard a signing operation. In passthrough mode, executes the callback.
2247
2436
  * For full Gate integration, use GateClient with evaluate() before sending.
@@ -2250,7 +2439,595 @@ var Gate = class {
2250
2439
  return cb();
2251
2440
  }
2252
2441
  };
2442
+ var AwsKmsSigner = class {
2443
+ config;
2444
+ constructor(config) {
2445
+ this.config = config;
2446
+ }
2447
+ getName() {
2448
+ return "AWS KMS";
2449
+ }
2450
+ isAvailable() {
2451
+ return !!this.config.kmsClient;
2452
+ }
2453
+ async sign(request) {
2454
+ if (!this.isAvailable()) {
2455
+ throw new Error("AWS KMS client not configured");
2456
+ }
2457
+ const algorithm = this.mapAlgorithm(request.algorithm || this.config.defaultAlgorithm || "ECDSA_SHA_256");
2458
+ const signInput = {
2459
+ KeyId: request.keyId,
2460
+ Message: Buffer.from(request.message),
2461
+ MessageType: request.messageType || this.config.defaultMessageType || "RAW",
2462
+ SigningAlgorithm: algorithm
2463
+ };
2464
+ const command = new SignCommand(signInput);
2465
+ const response = await this.config.kmsClient.send(command);
2466
+ if (!response.Signature) {
2467
+ throw new Error("AWS KMS sign response missing signature");
2468
+ }
2469
+ return {
2470
+ signature: Buffer.from(response.Signature),
2471
+ keyId: response.KeyId || request.keyId,
2472
+ algorithm: response.SigningAlgorithm || algorithm,
2473
+ metadata: {
2474
+ keyId: response.KeyId,
2475
+ signingAlgorithm: response.SigningAlgorithm
2476
+ }
2477
+ };
2478
+ }
2479
+ /**
2480
+ * Map algorithm string to AWS KMS SigningAlgorithmSpec
2481
+ */
2482
+ mapAlgorithm(algorithm) {
2483
+ if (Object.values(SigningAlgorithmSpec).includes(algorithm)) {
2484
+ return algorithm;
2485
+ }
2486
+ const algorithmMap = {
2487
+ "ECDSA_SHA_256": SigningAlgorithmSpec.ECDSA_SHA_256,
2488
+ "ECDSA_SHA_384": SigningAlgorithmSpec.ECDSA_SHA_384,
2489
+ "ECDSA_SHA_512": SigningAlgorithmSpec.ECDSA_SHA_512,
2490
+ "RSASSA_PSS_SHA_256": SigningAlgorithmSpec.RSASSA_PSS_SHA_256,
2491
+ "RSASSA_PSS_SHA_384": SigningAlgorithmSpec.RSASSA_PSS_SHA_384,
2492
+ "RSASSA_PSS_SHA_512": SigningAlgorithmSpec.RSASSA_PSS_SHA_512,
2493
+ "RSASSA_PKCS1_V1_5_SHA_256": SigningAlgorithmSpec.RSASSA_PKCS1_V1_5_SHA_256,
2494
+ "RSASSA_PKCS1_V1_5_SHA_384": SigningAlgorithmSpec.RSASSA_PKCS1_V1_5_SHA_384,
2495
+ "RSASSA_PKCS1_V1_5_SHA_512": SigningAlgorithmSpec.RSASSA_PKCS1_V1_5_SHA_512
2496
+ };
2497
+ return algorithmMap[algorithm.toUpperCase()] || SigningAlgorithmSpec.ECDSA_SHA_256;
2498
+ }
2499
+ };
2500
+
2501
+ // src/signer/VaultSigner.ts
2502
+ var VaultSigner = class {
2503
+ config;
2504
+ authToken = null;
2505
+ constructor(config) {
2506
+ this.config = {
2507
+ mountPath: "transit",
2508
+ ...config
2509
+ };
2510
+ }
2511
+ getName() {
2512
+ return "HashiCorp Vault";
2513
+ }
2514
+ isAvailable() {
2515
+ return !!this.config.vaultUrl && (!!this.config.token || !!this.config.appRole);
2516
+ }
2517
+ async sign(request) {
2518
+ if (!this.isAvailable()) {
2519
+ throw new Error("Vault signer not configured");
2520
+ }
2521
+ if (!this.authToken && this.config.appRole) {
2522
+ await this.authenticateAppRole();
2523
+ }
2524
+ const token = this.config.token || this.authToken;
2525
+ if (!token) {
2526
+ throw new Error("Vault authentication token not available");
2527
+ }
2528
+ const algorithm = this.mapAlgorithm(request.algorithm || this.config.defaultAlgorithm || "ecdsa-sha2-256");
2529
+ const url = `${this.config.vaultUrl}/v1/${this.config.mountPath}/sign/${request.keyId}`;
2530
+ const messageBase64 = Buffer.from(request.message).toString("base64");
2531
+ const requestBody = {
2532
+ input: messageBase64,
2533
+ ...algorithm && { algorithm },
2534
+ ...request.options || {}
2535
+ };
2536
+ const timeout = this.config.httpOptions?.timeout || 5e3;
2537
+ const controller = new AbortController();
2538
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
2539
+ try {
2540
+ const response = await fetch(url, {
2541
+ method: "POST",
2542
+ headers: {
2543
+ "Content-Type": "application/json",
2544
+ "X-Vault-Token": token
2545
+ },
2546
+ body: JSON.stringify(requestBody),
2547
+ signal: controller.signal
2548
+ });
2549
+ clearTimeout(timeoutId);
2550
+ if (!response.ok) {
2551
+ const errorText = await response.text();
2552
+ throw new Error(`Vault sign failed: ${response.status} ${errorText}`);
2553
+ }
2554
+ const data = await response.json();
2555
+ if (!data.data || !data.data.signature) {
2556
+ throw new Error("Vault sign response missing signature");
2557
+ }
2558
+ const signatureParts = data.data.signature.split(":");
2559
+ const signatureBase64 = signatureParts[signatureParts.length - 1];
2560
+ const signature = Buffer.from(signatureBase64, "base64");
2561
+ return {
2562
+ signature,
2563
+ keyId: request.keyId,
2564
+ algorithm,
2565
+ metadata: {
2566
+ vaultSignature: data.data.signature,
2567
+ keyVersion: data.data.key_version
2568
+ }
2569
+ };
2570
+ } catch (error) {
2571
+ clearTimeout(timeoutId);
2572
+ if (error.name === "AbortError") {
2573
+ throw new Error("Vault sign request timeout");
2574
+ }
2575
+ throw error;
2576
+ }
2577
+ }
2578
+ /**
2579
+ * Authenticate using AppRole
2580
+ */
2581
+ async authenticateAppRole() {
2582
+ if (!this.config.appRole) {
2583
+ throw new Error("AppRole not configured");
2584
+ }
2585
+ const url = `${this.config.vaultUrl}/v1/auth/approle/login`;
2586
+ const response = await fetch(url, {
2587
+ method: "POST",
2588
+ headers: {
2589
+ "Content-Type": "application/json"
2590
+ },
2591
+ body: JSON.stringify({
2592
+ role_id: this.config.appRole.roleId,
2593
+ secret_id: this.config.appRole.secretId
2594
+ })
2595
+ });
2596
+ if (!response.ok) {
2597
+ const errorText = await response.text();
2598
+ throw new Error(`Vault AppRole authentication failed: ${response.status} ${errorText}`);
2599
+ }
2600
+ const data = await response.json();
2601
+ if (!data.auth || !data.auth.client_token) {
2602
+ throw new Error("Vault AppRole authentication response missing token");
2603
+ }
2604
+ this.authToken = data.auth.client_token;
2605
+ }
2606
+ /**
2607
+ * Map algorithm string to Vault format
2608
+ */
2609
+ mapAlgorithm(algorithm) {
2610
+ const algorithmMap = {
2611
+ "ECDSA_SHA_256": "ecdsa-sha2-256",
2612
+ "ECDSA_SHA_384": "ecdsa-sha2-384",
2613
+ "ECDSA_SHA_512": "ecdsa-sha2-512",
2614
+ "RSASSA_PSS_SHA_256": "rsa-sha2-256",
2615
+ "RSASSA_PSS_SHA_384": "rsa-sha2-384",
2616
+ "RSASSA_PSS_SHA_512": "rsa-sha2-512"
2617
+ };
2618
+ if (algorithm.startsWith("ecdsa-") || algorithm.startsWith("rsa-")) {
2619
+ return algorithm;
2620
+ }
2621
+ return algorithmMap[algorithm.toUpperCase()] || "ecdsa-sha2-256";
2622
+ }
2623
+ };
2624
+
2625
+ // src/signer/GcpKmsSigner.ts
2626
+ var GcpKmsSigner = class {
2627
+ config;
2628
+ accessToken = null;
2629
+ tokenExpiry = 0;
2630
+ constructor(config) {
2631
+ this.config = {
2632
+ useWorkloadIdentity: false,
2633
+ ...config
2634
+ };
2635
+ }
2636
+ getName() {
2637
+ return "Google Cloud KMS";
2638
+ }
2639
+ isAvailable() {
2640
+ if (this.config.useWorkloadIdentity) {
2641
+ return true;
2642
+ }
2643
+ return !!this.config.credentials && !!this.config.projectId;
2644
+ }
2645
+ async sign(request) {
2646
+ if (!this.isAvailable()) {
2647
+ throw new Error("GCP KMS signer not configured");
2648
+ }
2649
+ const accessToken = await this.getAccessToken();
2650
+ const algorithm = this.mapAlgorithm(request.algorithm || this.config.defaultAlgorithm || "EC_SIGN_P256_SHA256");
2651
+ const keyName = request.keyId.includes("/") ? request.keyId : `projects/${this.config.projectId}/locations/${this.config.location}/keyRings/${this.config.keyRing}/cryptoKeys/${request.keyId}`;
2652
+ const url = `https://cloudkms.googleapis.com/v1/${keyName}:asymmetricSign`;
2653
+ const messageBase64 = Buffer.from(request.message).toString("base64");
2654
+ const requestBody = {
2655
+ digest: {
2656
+ sha256: messageBase64
2657
+ // GCP expects digest, not raw message
2658
+ }
2659
+ };
2660
+ const timeout = this.config.httpOptions?.timeout || 5e3;
2661
+ const controller = new AbortController();
2662
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
2663
+ try {
2664
+ const response = await fetch(url, {
2665
+ method: "POST",
2666
+ headers: {
2667
+ "Content-Type": "application/json",
2668
+ "Authorization": `Bearer ${accessToken}`
2669
+ },
2670
+ body: JSON.stringify(requestBody),
2671
+ signal: controller.signal
2672
+ });
2673
+ clearTimeout(timeoutId);
2674
+ if (!response.ok) {
2675
+ const errorText = await response.text();
2676
+ throw new Error(`GCP KMS sign failed: ${response.status} ${errorText}`);
2677
+ }
2678
+ const data = await response.json();
2679
+ if (!data.signature) {
2680
+ throw new Error("GCP KMS sign response missing signature");
2681
+ }
2682
+ const signature = Buffer.from(data.signature, "base64");
2683
+ return {
2684
+ signature,
2685
+ keyId: request.keyId,
2686
+ algorithm,
2687
+ metadata: {
2688
+ name: data.name,
2689
+ verifiedDigestCrc32c: data.verifiedDigestCrc32c
2690
+ }
2691
+ };
2692
+ } catch (error) {
2693
+ clearTimeout(timeoutId);
2694
+ if (error.name === "AbortError") {
2695
+ throw new Error("GCP KMS sign request timeout");
2696
+ }
2697
+ throw error;
2698
+ }
2699
+ }
2700
+ /**
2701
+ * Get GCP access token
2702
+ */
2703
+ async getAccessToken() {
2704
+ if (this.accessToken && Date.now() < this.tokenExpiry - 5 * 60 * 1e3) {
2705
+ return this.accessToken;
2706
+ }
2707
+ if (this.config.useWorkloadIdentity) {
2708
+ const metadataUrl = "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token";
2709
+ const response = await fetch(metadataUrl, {
2710
+ method: "GET",
2711
+ headers: {
2712
+ "Metadata-Flavor": "Google"
2713
+ }
2714
+ });
2715
+ if (!response.ok) {
2716
+ throw new Error(`GCP metadata service authentication failed: ${response.status}`);
2717
+ }
2718
+ const data = await response.json();
2719
+ this.accessToken = data.access_token;
2720
+ this.tokenExpiry = Date.now() + data.expires_in * 1e3;
2721
+ return data.access_token;
2722
+ } else {
2723
+ if (!this.config.credentials) {
2724
+ throw new Error("GCP credentials not configured");
2725
+ }
2726
+ throw new Error("Service account authentication requires @google-cloud/kms SDK. Install it with: npm install @google-cloud/kms. Alternatively, use workload identity (recommended for GCP environments).");
2727
+ }
2728
+ }
2729
+ /**
2730
+ * Map algorithm string to GCP format
2731
+ */
2732
+ mapAlgorithm(algorithm) {
2733
+ const algorithmMap = {
2734
+ "ECDSA_SHA_256": "EC_SIGN_P256_SHA256",
2735
+ "ECDSA_SHA_384": "EC_SIGN_P384_SHA384",
2736
+ "ECDSA_SHA_512": "EC_SIGN_P512_SHA512",
2737
+ "RSASSA_PSS_SHA_256": "RSA_SIGN_PSS_2048_SHA256",
2738
+ "RSASSA_PSS_SHA_384": "RSA_SIGN_PSS_3072_SHA256",
2739
+ "RSASSA_PSS_SHA_512": "RSA_SIGN_PSS_4096_SHA256",
2740
+ "RSASSA_PKCS1_V1_5_SHA_256": "RSA_SIGN_PKCS1_2048_SHA256",
2741
+ "RSASSA_PKCS1_V1_5_SHA_384": "RSA_SIGN_PKCS1_3072_SHA256",
2742
+ "RSASSA_PKCS1_V1_5_SHA_512": "RSA_SIGN_PKCS1_4096_SHA256"
2743
+ };
2744
+ if (algorithm.startsWith("EC_SIGN_") || algorithm.startsWith("RSA_SIGN_")) {
2745
+ return algorithm;
2746
+ }
2747
+ return algorithmMap[algorithm.toUpperCase()] || "EC_SIGN_P256_SHA256";
2748
+ }
2749
+ };
2750
+ var FireblocksSigner = class {
2751
+ config;
2752
+ apiBaseUrl;
2753
+ constructor(config) {
2754
+ this.config = config;
2755
+ this.apiBaseUrl = config.apiBaseUrl ?? "https://api.fireblocks.io";
2756
+ }
2757
+ getName() {
2758
+ return "Fireblocks";
2759
+ }
2760
+ isAvailable() {
2761
+ return !!this.config.apiKey && !!this.config.apiSecret;
2762
+ }
2763
+ async sign(request) {
2764
+ if (!this.isAvailable()) {
2765
+ throw new Error("Fireblocks API key and secret required");
2766
+ }
2767
+ const keyIdMatch = request.keyId.match(/^fireblocks:\/\/([^/]+)\/(.+)$/);
2768
+ if (!keyIdMatch) {
2769
+ throw new Error(
2770
+ "Invalid Fireblocks keyId format. Expected: fireblocks://vaultAccountId/assetId"
2771
+ );
2772
+ }
2773
+ const [, vaultAccountId, assetId] = keyIdMatch;
2774
+ const messageHex = request.message instanceof Buffer ? request.message.toString("hex") : Buffer.from(request.message).toString("hex");
2775
+ const requestId = request.options?.requestId || request.requestId;
2776
+ const txRequest = {
2777
+ operation: "RAW",
2778
+ source: { type: "VAULT_ACCOUNT", id: vaultAccountId },
2779
+ assetId,
2780
+ note: `Gate signing request: ${requestId ?? "unknown"}`,
2781
+ extraParameters: {
2782
+ rawMessageData: {
2783
+ messages: [{ content: messageHex }]
2784
+ }
2785
+ }
2786
+ };
2787
+ const token = this.createAuthToken("/v1/transactions", JSON.stringify(txRequest));
2788
+ const response = await fetch(`${this.apiBaseUrl}/v1/transactions`, {
2789
+ method: "POST",
2790
+ headers: {
2791
+ "Content-Type": "application/json",
2792
+ "X-API-Key": this.config.apiKey,
2793
+ Authorization: `Bearer ${token}`
2794
+ },
2795
+ body: JSON.stringify(txRequest)
2796
+ });
2797
+ if (!response.ok) {
2798
+ const error = await response.text();
2799
+ throw new Error(`Fireblocks API error: ${response.status} ${error}`);
2800
+ }
2801
+ const result = await response.json();
2802
+ const txId = result.id;
2803
+ if (!txId) {
2804
+ throw new Error("Fireblocks API did not return transaction id");
2805
+ }
2806
+ const signed = await this.pollTransaction(txId);
2807
+ const sigHex = signed?.signature ?? signed?.signedMessages?.[0]?.signature;
2808
+ if (!sigHex) {
2809
+ throw new Error(`Fireblocks transaction ${txId} did not return signature`);
2810
+ }
2811
+ return {
2812
+ signature: Buffer.from(sigHex, "hex"),
2813
+ keyId: request.keyId,
2814
+ algorithm: request.algorithm ?? "ECDSA_SHA_256"
2815
+ };
2816
+ }
2817
+ /**
2818
+ * Create JWT for Fireblocks API (RS256, uri + bodyHash in payload).
2819
+ */
2820
+ createAuthToken(uri, bodyJson) {
2821
+ const now = Math.floor(Date.now() / 1e3);
2822
+ const nonce = randomBytes(16).toString("hex");
2823
+ const bodyHash = bodyJson ? createHash("sha256").update(bodyJson, "utf8").digest("hex") : "";
2824
+ const payload = {
2825
+ uri,
2826
+ nonce,
2827
+ iat: now,
2828
+ exp: now + 30,
2829
+ sub: this.config.apiKey,
2830
+ bodyHash
2831
+ };
2832
+ const header = { alg: "RS256", typ: "JWT" };
2833
+ const encodedHeader = base64UrlEncode(JSON.stringify(header));
2834
+ const encodedPayload = base64UrlEncode(JSON.stringify(payload));
2835
+ const signingInput = `${encodedHeader}.${encodedPayload}`;
2836
+ const sign = createSign("RSA-SHA256");
2837
+ sign.update(signingInput);
2838
+ const signature = sign.sign(this.config.apiSecret);
2839
+ const encodedSig = base64UrlEncode(signature);
2840
+ return `${signingInput}.${encodedSig}`;
2841
+ }
2842
+ async pollTransaction(txId, maxAttempts = 30) {
2843
+ for (let i = 0; i < maxAttempts; i++) {
2844
+ const token = this.createAuthToken(`/v1/transactions/${txId}`);
2845
+ const response = await fetch(`${this.apiBaseUrl}/v1/transactions/${txId}`, {
2846
+ headers: {
2847
+ "X-API-Key": this.config.apiKey,
2848
+ Authorization: `Bearer ${token}`
2849
+ }
2850
+ });
2851
+ if (!response.ok) {
2852
+ throw new Error(`Failed to fetch transaction status: ${await response.text()}`);
2853
+ }
2854
+ const tx = await response.json();
2855
+ if (tx.status === "COMPLETED") {
2856
+ return tx.signedMessages?.[0] ? { signature: tx.signedMessages[0].signature } : tx;
2857
+ }
2858
+ if (tx.status === "FAILED" || tx.status === "REJECTED") {
2859
+ throw new Error(`Fireblocks transaction ${txId} failed: ${tx.status}`);
2860
+ }
2861
+ await new Promise((r) => setTimeout(r, 1e3));
2862
+ }
2863
+ throw new Error(
2864
+ `Fireblocks transaction ${txId} did not complete within ${maxAttempts} seconds`
2865
+ );
2866
+ }
2867
+ };
2868
+ function base64UrlEncode(input) {
2869
+ const raw = typeof input === "string" ? Buffer.from(input, "utf8").toString("base64") : input.toString("base64");
2870
+ return raw.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
2871
+ }
2872
+ var require2 = createRequire(import.meta.url);
2873
+ var NOT_LINKED = "PKCS#11 runtime not linked. Install pkcs11js (npm install pkcs11js) and ensure the HSM library path is correct, or provide a custom pkcs11Session to GenericHsmSigner.";
2874
+ function mechanismToPkcs11(mechanism) {
2875
+ switch (mechanism) {
2876
+ case "CKM_ECDSA_SHA256":
2877
+ return getPkcs11().CKM_ECDSA_SHA256;
2878
+ case "CKM_RSA_PKCS":
2879
+ return getPkcs11().CKM_SHA256_RSA_PKCS;
2880
+ default:
2881
+ throw new Error(`Unsupported PKCS#11 mechanism: ${mechanism}`);
2882
+ }
2883
+ }
2884
+ var pkcs11Module = void 0;
2885
+ function getPkcs11() {
2886
+ if (pkcs11Module !== void 0) {
2887
+ if (pkcs11Module === null) throw new Error(NOT_LINKED);
2888
+ return pkcs11Module;
2889
+ }
2890
+ try {
2891
+ pkcs11Module = require2("pkcs11js");
2892
+ return pkcs11Module;
2893
+ } catch {
2894
+ pkcs11Module = null;
2895
+ throw new Error(NOT_LINKED);
2896
+ }
2897
+ }
2898
+ var Pkcs11SessionImpl = class {
2899
+ libPath = "";
2900
+ pin = "";
2901
+ pkcs11 = null;
2902
+ session = null;
2903
+ initialized = false;
2904
+ async initialize(libraryPath, pin, options) {
2905
+ const p = getPkcs11();
2906
+ this.libPath = libraryPath;
2907
+ this.pin = pin;
2908
+ this.pkcs11 = new p.PKCS11();
2909
+ this.pkcs11.load(libraryPath);
2910
+ this.pkcs11.C_Initialize();
2911
+ this.initialized = true;
2912
+ const slots = this.pkcs11.C_GetSlotList(true);
2913
+ if (!slots || slots.length === 0) {
2914
+ await this.close();
2915
+ throw new Error("PKCS#11: no token present in any slot");
2916
+ }
2917
+ const slotIndex = options?.slotId ?? 0;
2918
+ if (slotIndex < 0 || slotIndex >= slots.length) {
2919
+ await this.close();
2920
+ throw new Error(`PKCS#11: slotId ${slotIndex} out of range (0..${slots.length - 1})`);
2921
+ }
2922
+ const slot = slots[slotIndex];
2923
+ const flags = p.CKF_SERIAL_SESSION | p.CKF_RW_SESSION;
2924
+ this.session = this.pkcs11.C_OpenSession(slot, flags);
2925
+ this.pkcs11.C_Login(this.session, p.CKU_USER, pin);
2926
+ }
2927
+ async sign(keyHandle, mechanism, data) {
2928
+ if (!this.pkcs11 || !this.session) {
2929
+ throw new Error("PKCS#11 session not initialized. Call initialize() first.");
2930
+ }
2931
+ getPkcs11();
2932
+ const mechCode = mechanismToPkcs11(mechanism);
2933
+ this.pkcs11.C_SignInit(this.session, { mechanism: mechCode }, keyHandle);
2934
+ const maxSigLen = 512;
2935
+ const outData = Buffer.alloc(maxSigLen);
2936
+ const signature = this.pkcs11.C_Sign(this.session, data, outData);
2937
+ return Buffer.from(signature);
2938
+ }
2939
+ async close() {
2940
+ if (!this.initialized) return;
2941
+ this.initialized = false;
2942
+ try {
2943
+ if (this.pkcs11 && this.session) {
2944
+ try {
2945
+ this.pkcs11.C_Logout(this.session);
2946
+ } catch {
2947
+ }
2948
+ try {
2949
+ this.pkcs11.C_CloseSession(this.session);
2950
+ } catch {
2951
+ }
2952
+ }
2953
+ if (this.pkcs11) {
2954
+ try {
2955
+ this.pkcs11.C_Finalize();
2956
+ } catch {
2957
+ }
2958
+ try {
2959
+ this.pkcs11.close();
2960
+ } catch {
2961
+ }
2962
+ }
2963
+ } finally {
2964
+ this.pkcs11 = null;
2965
+ this.session = null;
2966
+ }
2967
+ }
2968
+ };
2969
+
2970
+ // src/signer/GenericHsmSigner.ts
2971
+ var GenericHsmSigner = class {
2972
+ config;
2973
+ session = null;
2974
+ constructor(config) {
2975
+ this.config = config;
2976
+ }
2977
+ getName() {
2978
+ return "Generic HSM (PKCS#11)";
2979
+ }
2980
+ isAvailable() {
2981
+ return !!this.config.pkcs11LibraryPath && !!this.config.pin;
2982
+ }
2983
+ async sign(request) {
2984
+ if (!this.session) {
2985
+ this.session = this.config.pkcs11Session ?? await this.initializePkcs11Session();
2986
+ }
2987
+ const keyIdMatch = request.keyId.match(/^hsm:\/\/(.+)$/);
2988
+ if (!keyIdMatch) {
2989
+ throw new Error(
2990
+ "Invalid HSM keyId format. Expected: hsm://keyHandle (hex-encoded) or hsm://keyLabel"
2991
+ );
2992
+ }
2993
+ const keyHandle = Buffer.from(keyIdMatch[1], "hex");
2994
+ const mechanism = this.mapAlgorithmToMechanism(
2995
+ request.algorithm ?? "ECDSA_SHA_256"
2996
+ );
2997
+ const message = request.message instanceof Buffer ? request.message : Buffer.from(request.message);
2998
+ const signature = await this.session.sign(keyHandle, mechanism, message);
2999
+ return {
3000
+ signature,
3001
+ keyId: request.keyId,
3002
+ algorithm: request.algorithm ?? "ECDSA_SHA_256"
3003
+ };
3004
+ }
3005
+ async initializePkcs11Session() {
3006
+ const session = new Pkcs11SessionImpl();
3007
+ await session.initialize(this.config.pkcs11LibraryPath, this.config.pin, {
3008
+ slotId: this.config.slotId
3009
+ });
3010
+ return session;
3011
+ }
3012
+ mapAlgorithmToMechanism(algorithm) {
3013
+ switch (algorithm) {
3014
+ case "ECDSA_SHA_256":
3015
+ return "CKM_ECDSA_SHA256";
3016
+ case "RSASSA_PKCS1_V1_5_SHA_256":
3017
+ return "CKM_RSA_PKCS";
3018
+ default:
3019
+ throw new Error(`Unsupported algorithm for HSM: ${algorithm}`);
3020
+ }
3021
+ }
3022
+ /** Release the PKCS#11 session. Call when done to free resources. */
3023
+ async close() {
3024
+ if (this.session) {
3025
+ await this.session.close();
3026
+ this.session = null;
3027
+ }
3028
+ }
3029
+ };
2253
3030
 
2254
- export { BlockIntelAuthError, BlockIntelBlockedError, BlockIntelStepUpRequiredError, BlockIntelUnavailableError, Gate, GateClient, GateError, GateErrorCode, HeartbeatManager, ProvenanceProvider, StepUpNotConfiguredError, buildTxBindingObject, computeTxDigest, createGateClient, GateClient as default, wrapKmsClient };
3031
+ export { AwsKmsSigner, BlockIntelAuthError, BlockIntelBlockedError, BlockIntelStepUpRequiredError, BlockIntelUnavailableError, FireblocksSigner, Gate, GateClient, GateError, GateErrorCode, GcpKmsSigner, GenericHsmSigner, HeartbeatManager, ProvenanceProvider, StepUpNotConfiguredError, VaultSigner, buildTxBindingObject, computeTxDigest, createGateClient, GateClient as default, wrapKmsClient };
2255
3032
  //# sourceMappingURL=index.js.map
2256
3033
  //# sourceMappingURL=index.js.map