blockintel-gate-sdk 0.3.8 → 0.3.9

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