Skip to content

feat: implement encryption and access control features#28

Merged
Patrick-Ehimen merged 2 commits intomainfrom
feat/encryption-access-control
Oct 23, 2025
Merged

feat: implement encryption and access control features#28
Patrick-Ehimen merged 2 commits intomainfrom
feat/encryption-access-control

Conversation

@Patrick-Ehimen
Copy link
Copy Markdown
Owner

@Patrick-Ehimen Patrick-Ehimen commented Oct 18, 2025

📋 Summary

This PR implements comprehensive encryption and access control capabilities to the MCP server and SDK wrapper through Kavach SDK integration.

🎯 What This PR Does

  • ✅ Adds threshold cryptography for secure key management
  • ✅ Implements smart contract-based access control conditions
  • ✅ Creates new MCP tools for encryption operations
  • ✅ Extends SDK with comprehensive encryption methods
  • ✅ Maintains backward compatibility with existing functionality

🚀 Key Features Implemented

1. Threshold Cryptography

  • Generate master keys split into configurable shards (3-15 total)
  • Require minimum threshold (2-10) for key recovery
  • Secure key distribution across distributed nodes
  • BLS cryptography for enhanced security

2. Access Control System

  • Smart contract-based access conditions
  • Support for EVM (Ethereum, Polygon, etc.) and Solana networks
  • Flexible condition types: ERC20/ERC721/ERC1155/Custom contracts
  • Complex return value testing with multiple operators (==, >=, <=, !=, >, <)

3. New MCP Tools

  • lighthouse_generate_key: Generate threshold encryption keys
  • lighthouse_setup_access_control: Configure file access conditions

4. Enhanced SDK Wrapper

  • Complete encryption methods with circuit breaker protection
  • Event-driven architecture for progress tracking
  • JWT authentication workflow for encryption operations
  • Graceful fallback when Kavach SDK unavailable

Summary by Sourcery

Enable end-to-end encryption and fine-grained access control by integrating the Kavach SDK: extend the JS SDK with threshold cryptography methods and event forwarding, enhance the MCP server with corresponding service methods and tools, and add comprehensive tests to validate all new workflows

New Features:

  • Integrate threshold cryptography for key generation, sharding, recovery, and secure key distribution
  • Implement smart contract–based access control for encrypted files across EVM and Solana networks
  • Add EncryptionManager to wrap Kavach SDK and extend LighthouseAISDK with encryption methods and events
  • Introduce new MCP tools for generating encryption keys and configuring access control

Enhancements:

  • Extend SDK wrapper with circuit breaker protection, retry logic, and backward-compatible APIs for encryption operations
  • Export new types for encryption options and access control configurations
  • Provide mock implementations for encryption and access control in testing environments

Build:

  • Add @lighthouse-web3/kavach dependency to enable encryption features

Tests:

  • Add unit tests for EncryptionManager covering all encryption workflows and events
  • Update integration tests to verify registration of new MCP tools

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai bot commented Oct 18, 2025

Reviewer's Guide

This PR integrates the Kavach SDK to provide threshold‐based encryption and smart contract–driven access control across both the SDK wrapper and the MCP server. It introduces an EncryptionManager in the SDK layer with new methods and event forwarding, updates type definitions, extends the server service and tools to expose key generation and access control setup, and adds corresponding tests and dependencies while preserving existing functionality.

Sequence diagram for encryption key generation via MCP tool

sequenceDiagram
  participant User
  participant MCPServer
  participant LighthouseGenerateKeyTool
  participant LighthouseService
  participant LighthouseAISDK
  participant EncryptionManager

  User->>MCPServer: Call lighthouse_generate_key tool (threshold, keyCount)
  MCPServer->>LighthouseGenerateKeyTool: execute(args)
  LighthouseGenerateKeyTool->>LighthouseService: generateEncryptionKey(threshold, keyCount)
  LighthouseService->>LighthouseAISDK: generateEncryptionKey(threshold, keyCount)
  LighthouseAISDK->>EncryptionManager: generateKey(threshold, keyCount)
  EncryptionManager-->>LighthouseAISDK: GeneratedKey
  LighthouseAISDK-->>LighthouseService: GeneratedKey
  LighthouseService-->>LighthouseGenerateKeyTool: { success, data }
  LighthouseGenerateKeyTool-->>MCPServer: ProgressAwareToolResult
  MCPServer-->>User: Return key shards
Loading

Sequence diagram for access control setup via MCP tool

sequenceDiagram
  participant User
  participant MCPServer
  participant LighthouseSetupAccessControlTool
  participant LighthouseService
  participant LighthouseAISDK
  participant EncryptionManager

  User->>MCPServer: Call lighthouse_setup_access_control tool (address, cid, conditions, keyShards, authToken)
  MCPServer->>LighthouseSetupAccessControlTool: execute(args)
  LighthouseSetupAccessControlTool->>LighthouseService: setupAccessControl(config, authToken)
  LighthouseService->>LighthouseAISDK: setupAccessControl(config, authToken)
  LighthouseAISDK->>EncryptionManager: setupAccessControl(config, authToken)
  EncryptionManager-->>LighthouseAISDK: EncryptionResponse
  LighthouseAISDK-->>LighthouseService: EncryptionResponse
  LighthouseService-->>LighthouseSetupAccessControlTool: { success }
  LighthouseSetupAccessControlTool-->>MCPServer: ProgressAwareToolResult
  MCPServer-->>User: Return setup status
Loading

Class diagram for new EncryptionManager and SDK integration

classDiagram
  class LighthouseAISDK {
    - progress: ProgressTracker
    - errorHandler: ErrorHandler
    - circuitBreaker: CircuitBreaker
    - encryption: EncryptionManager
    - config: LighthouseConfig
    + generateEncryptionKey(threshold, keyCount): Promise<GeneratedKey>
    + shardEncryptionKey(masterKey, threshold, keyCount): Promise<keyShards: KeyShard[]>
    + setupAccessControl(config, authToken): Promise<EncryptionResponse>
    + recoverEncryptionKey(keyShards): Promise<masterKey: string | null; error: string | null>
    + shareFileAccess(cid, ownerAddress, shareToAddress, authToken): Promise<EncryptionResponse>
    + getEncryptionAuthMessage(address): Promise<message: string | null; error: string | null>
    + getEncryptionJWT(address, signedMessage): Promise<string | null>
    + isEncryptionAvailable(): boolean
    + destroy(): void
  }
  class EncryptionManager {
    + isAvailable(): boolean
    + generateKey(threshold, keyCount): Promise<GeneratedKey>
    + shardKey(masterKey, threshold, keyCount): Promise<keyShards: KeyShard[]>
    + saveKeyShards(address, cid, authToken, keyShards, sharedTo): Promise<EncryptionResponse>
    + setupAccessControl(config, authToken): Promise<EncryptionResponse>
    + recoverKey(keyShards): Promise<masterKey: string | null; error: string | null>
    + getAuthMessage(address): Promise<message: string | null; error: string | null>
    + getJWT(address, signedMessage): Promise<string | null>
    + shareToAddress(cid, address, shareToAddress, authToken): Promise<EncryptionResponse>
    + destroy(): void
  }
  LighthouseAISDK --> EncryptionManager

  class GeneratedKey {
    masterKey: string | null
    keyShards: KeyShard[]
  }
  class KeyShard {
    key: string
    index: string
  }
  class AccessControlConfig {
    address: string
    cid: string
    conditions: EnhancedAccessCondition[]
    aggregator: string
    chainType: ChainType
    keyShards: KeyShard[]
    decryptionType: DecryptionType
  }
  class EncryptionResponse {
    isSuccess: boolean
    error: string | null
    keyShards: KeyShard[]
    masterKey: string
  }
  EncryptionManager --> GeneratedKey
  GeneratedKey --> KeyShard
  EncryptionManager --> EncryptionResponse
  EncryptionManager --> AccessControlConfig

  class EnhancedAccessCondition
  class EVMAccessCondition {
    id: number
    standardContractType: string
    contractAddress: string
    chain: string
    method: string
    parameters: any[]
    returnValueTest: ReturnValueTest
    inputArrayType: string[]
    outputType: string
  }
  class SolanaAccessCondition {
    id: number
    contractAddress: string
    chain: string
    method: string
    standardContractType: string
    parameters: any[]
    pdaInterface: object
    returnValueTest: ReturnValueTest
  }
  EnhancedAccessCondition <|-- EVMAccessCondition
  EnhancedAccessCondition <|-- SolanaAccessCondition

  class ReturnValueTest {
    comparator: string
    value: any
  }
  EVMAccessCondition --> ReturnValueTest
  SolanaAccessCondition --> ReturnValueTest
Loading

Class diagram for MCP server service and new tools

classDiagram
  class ILighthouseService {
    + generateEncryptionKey(threshold, keyCount): Promise
    + setupAccessControl(config, authToken): Promise
  }
  class LighthouseService {
    + generateEncryptionKey(threshold, keyCount): Promise
    + setupAccessControl(config, authToken): Promise
  }
  class MockLighthouseService {
    + generateEncryptionKey(threshold, keyCount): Promise
    + setupAccessControl(config, authToken): Promise
  }
  ILighthouseService <|.. LighthouseService
  ILighthouseService <|.. MockLighthouseService

  class LighthouseGenerateKeyTool {
    + execute(args): ProgressAwareToolResult
    + static getDefinition(): MCPToolDefinition
  }
  class LighthouseSetupAccessControlTool {
    + execute(args): ProgressAwareToolResult
    + static getDefinition(): MCPToolDefinition
  }
  LighthouseGenerateKeyTool --> ILighthouseService
  LighthouseSetupAccessControlTool --> ILighthouseService
Loading

File-Level Changes

Change Details Files
Extend LighthouseAISDK with a new EncryptionManager and related methods
  • Import and initialize EncryptionManager in the SDK constructor
  • Forward encryption lifecycle events through the SDK’s event emitter
  • Add wrapper methods: generateEncryptionKey, shardEncryptionKey, recoverEncryptionKey, setupAccessControl, shareFileAccess, getEncryptionAuthMessage, getEncryptionJWT, isEncryptionAvailable
  • Include EncryptionManager cleanup in destroy()
packages/sdk-wrapper/src/LighthouseAISDK.ts
packages/sdk-wrapper/src/index.ts
Implement EncryptionManager to wrap Kavach SDK functionality
  • Load Kavach SDK with fallback and track its availability
  • Define methods for key generation, sharding, recovery, access control, sharing, JWT workflow
  • Emit start/success/error events for each operation
  • Provide destroy() to clean up listeners
packages/sdk-wrapper/src/encryption/EncryptionManager.ts
Expand type definitions to support encryption and access control
  • Add KeyShard, GeneratedKey, EncryptionOptions, AccessControlConfig, EncryptionResponse, AuthToken types
  • Define EVM/Solana/EnhancedAccessCondition and related enums
  • Augment UploadOptions with encryptionOptions and accessConditions
packages/sdk-wrapper/src/types.ts
Extend MCP server services and interface to expose encryption operations
  • Add generateEncryptionKey and setupAccessControl methods to LighthouseService
  • Add mock implementations in MockLighthouseService
  • Update ILighthouseService interface to include optional encryption methods
apps/mcp-server/src/services/LighthouseService.ts
apps/mcp-server/src/services/MockLighthouseService.ts
apps/mcp-server/src/services/ILighthouseService.ts
Register and implement new MCP tools for key management and access control
  • Create LighthouseGenerateKeyTool with validation, execution, and response formatting
  • Create LighthouseSetupAccessControlTool with parameter validation and execution logic
  • Register both tools in server.ts
apps/mcp-server/src/server.ts
apps/mcp-server/src/tools/LighthouseGenerateKeyTool.ts
apps/mcp-server/src/tools/LighthouseSetupAccessControlTool.ts
Update tests and dependencies for the new encryption features
  • Adjust integration test to expect two additional tools
  • Add EncryptionManager unit tests mocking Kavach SDK
  • Add @lighthouse-web3/kavach to sdk-wrapper package.json
apps/mcp-server/src/tests/integration.test.ts
packages/sdk-wrapper/src/__tests__/EncryptionManager.test.ts
packages/sdk-wrapper/package.json

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes and they look great!

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location> `packages/sdk-wrapper/src/encryption/EncryptionManager.ts:96-100` </location>
<code_context>
+
+      const result = await kavach.shardKey(masterKey, threshold, keyCount);
+
+      if (!result || !result.isShardable || !result.keyShards) {
+        throw new Error("Failed to shard encryption key");
+      }
</code_context>

<issue_to_address>
**suggestion:** Error handling for shardKey may be too generic.

Consider using distinct error messages for unshardable keys and missing shards to improve diagnosability.

```suggestion
      const result = await kavach.shardKey(masterKey, threshold, keyCount);

      if (!result) {
        throw new Error("No result returned from shardKey");
      }
      if (!result.isShardable) {
        throw new Error("Master key is not shardable");
      }
      if (!result.keyShards) {
        throw new Error("Key shards missing from shardKey result");
      }
```
</issue_to_address>

### Comment 2
<location> `packages/sdk-wrapper/src/encryption/EncryptionManager.ts:136-140` </location>
<code_context>
+
+      const result = await kavach.saveShards(address, cid, authToken, keyShards, sharedTo);
+
+      if (!result) {
+        throw new Error("Failed to save key shards");
+      }
</code_context>

<issue_to_address>
**suggestion:** Null result checks may not cover all error cases.

If the SDK provides structured error objects, handle those directly to improve error reporting and propagation.

```suggestion
      const result = await kavach.saveShards(address, cid, authToken, keyShards, sharedTo);

      if (result && result.error) {
        // Handle structured error from SDK
        throw new Error(`Failed to save key shards: ${result.error.message || JSON.stringify(result.error)}`);
      }

      if (!result) {
        throw new Error("Failed to save key shards: No result returned from SDK");
      }
```
</issue_to_address>

### Comment 3
<location> `apps/mcp-server/src/tests/encryption-tools.test.ts:4` </location>
<code_context>
+      this.lighthouseService,
+      this.logger,
+    );

     // Register lighthouse_upload_file tool
     this.registry.register(
</code_context>

<issue_to_address>
**issue (testing):** Missing encryption and access control tool tests.

Please add integration and edge case tests for `lighthouse_generate_key` and `lighthouse_setup_access_control`, covering invalid parameters, error handling, and success/failure scenarios.
</issue_to_address>

### Comment 4
<location> `packages/sdk-wrapper/src/__tests__/EncryptionManager.test.ts:47-48` </location>
<code_context>
+      expect(result).toEqual(mockResult);
+    });
+
+    it("should generate encryption key with custom parameters", async () => {
+      const mockResult = {
+        masterKey: "mock-master-key",
+        keyShards: [
+          { key: "shard1", index: "index1" },
+          { key: "shard2", index: "index2" },
+          { key: "shard3", index: "index3" },
+        ],
+      };
+      mockKavach.generate.mockResolvedValue(mockResult);
+
+      const result = await encryptionManager.generateKey(2, 3);
+
+      expect(mockKavach.generate).toHaveBeenCalledWith(2, 3);
+      expect(result).toEqual(mockResult);
+    });
+
</code_context>

<issue_to_address>
**suggestion (testing):** No test for edge case: threshold > keyCount.

Add a test to ensure an error is thrown when threshold exceeds keyCount, confirming invalid configurations are handled.

```suggestion
      expect(result).toEqual(mockResult);
    });

    it("should throw an error when threshold exceeds keyCount", async () => {
      const threshold = 6;
      const keyCount = 5;
      const errorMessage = "Threshold cannot be greater than keyCount";
      mockKavach.generate.mockImplementationOnce(() => {
        throw new Error(errorMessage);
      });

      await expect(encryptionManager.generateKey(threshold, keyCount)).rejects.toThrow(errorMessage);
      expect(mockKavach.generate).toHaveBeenCalledWith(threshold, keyCount);
    });
```
</issue_to_address>

### Comment 5
<location> `packages/sdk-wrapper/src/__tests__/EncryptionManager.test.ts:67-78` </location>
<code_context>
+      expect(result).toEqual(mockResult);
+    });
+
+    it("should throw error when Kavach SDK is not available", async () => {
+      const encryptionManagerWithoutKavach = new (class extends EncryptionManager {
+        constructor() {
+          super();
+          (this as any).isKavachAvailable = false;
+        }
+      })();
+
+      await expect(encryptionManagerWithoutKavach.generateKey()).rejects.toThrow(
+        "Kavach SDK not available - encryption features disabled",
+      );
+    });
+
</code_context>

<issue_to_address>
**suggestion (testing):** No test for fallback to local encryption SDK.

Please add a test to confirm that the local encryption SDK is used when Kavach is unavailable, ensuring the fallback logic is covered.

```suggestion
    it("should throw error when Kavach SDK is not available", async () => {
      const encryptionManagerWithoutKavach = new (class extends EncryptionManager {
        constructor() {
          super();
          (this as any).isKavachAvailable = false;
        }
      })();

      await expect(encryptionManagerWithoutKavach.generateKey()).rejects.toThrow(
        "Kavach SDK not available - encryption features disabled",
      );
    });

    it("should fallback to local encryption SDK when Kavach is not available", async () => {
      // Mock local encryption SDK
      const mockLocalEncryptionSDK = {
        generate: jest.fn().mockResolvedValue("localResult"),
      };

      // Extend EncryptionManager to inject the mock local SDK
      const encryptionManagerWithLocalFallback = new (class extends EncryptionManager {
        constructor() {
          super();
          (this as any).isKavachAvailable = false;
          (this as any).localEncryptionSDK = mockLocalEncryptionSDK;
        }
      })();

      const result = await encryptionManagerWithLocalFallback.generateKey(5, 7);

      expect(mockLocalEncryptionSDK.generate).toHaveBeenCalledWith(5, 7);
      expect(result).toEqual("localResult");
    });
```
</issue_to_address>

### Comment 6
<location> `packages/sdk-wrapper/src/__tests__/EncryptionManager.test.ts:185` </location>
<code_context>
+  });
+
+  describe("recoverKey", () => {
+    it("should recover master key from shards", async () => {
+      const mockResult = { masterKey: "recovered-key", error: null };
+      mockKavach.recoverKey.mockResolvedValue(mockResult);
+
+      const keyShards: KeyShard[] = [
+        { key: "shard1", index: "index1" },
+        { key: "shard2", index: "index2" },
+        { key: "shard3", index: "index3" },
+      ];
+
+      const result = await encryptionManager.recoverKey(keyShards);
+
+      expect(mockKavach.recoverKey).toHaveBeenCalledWith(keyShards);
+      expect(result).toEqual(mockResult);
+    });
+
</code_context>

<issue_to_address>
**suggestion (testing):** No test for insufficient key shards during recovery.

Please add a test case that checks recovery with insufficient key shards, ensuring it fails and returns the correct error.

```suggestion
  describe("recoverKey", () => {
    it("should fail to recover master key with insufficient shards", async () => {
      const mockErrorResult = { masterKey: null, error: "Insufficient shards" };
      mockKavach.recoverKey.mockResolvedValue(mockErrorResult);

      const insufficientShards: KeyShard[] = [
        { key: "shard1", index: "index1" },
      ];

      const result = await encryptionManager.recoverKey(insufficientShards);

      expect(mockKavach.recoverKey).toHaveBeenCalledWith(insufficientShards);
      expect(result).toEqual(mockErrorResult);
    });
```
</issue_to_address>

### Comment 7
<location> `packages/sdk-wrapper/src/__tests__/EncryptionManager.test.ts:217-166` </location>
<code_context>
+  });
+
+  describe("shareToAddress", () => {
+    it("should share access to another address", async () => {
+      const mockResult = { isSuccess: true, error: null };
+      mockKavach.shareToAddress.mockResolvedValue(mockResult);
+
+      const result = await encryptionManager.shareToAddress(
+        "QmTest",
+        "0x123",
+        "0x456",
+        "jwt-token",
+      );
+
+      expect(mockKavach.shareToAddress).toHaveBeenCalledWith(
+        "QmTest",
+        "0x123",
+        "0x456",
+        "jwt-token",
+      );
+      expect(result).toEqual({ isSuccess: true, error: null });
+    });
+  });
</code_context>

<issue_to_address>
**suggestion (testing):** No test for sharing access to invalid addresses.

Add a test to cover sharing access to an invalid or malformed address, ensuring proper error handling and reporting.

Suggested implementation:

```typescript
  describe("shareToAddress", () => {

```

```typescript
    it("should share access to another address", async () => {
      const mockResult = { isSuccess: true, error: null };
      mockKavach.shareToAddress.mockResolvedValue(mockResult);

      const result = await encryptionManager.shareToAddress(
        "QmTest",
        "0x123",
        "0x456",
        "jwt-token",
      );

      expect(mockKavach.shareToAddress).toHaveBeenCalledWith(
        "QmTest",
        "0x123",
        "0x456",
        "jwt-token",
      );
      expect(result).toEqual({ isSuccess: true, error: null });
    });

    it("should handle error when sharing to an invalid address", async () => {
      const mockErrorResult = { isSuccess: false, error: "Invalid address format" };
      mockKavach.shareToAddress.mockResolvedValue(mockErrorResult);

      const result = await encryptionManager.shareToAddress(
        "QmTest",
        "0x123",
        "invalid-address",
        "jwt-token",
      );

      expect(mockKavach.shareToAddress).toHaveBeenCalledWith(
        "QmTest",
        "0x123",
        "invalid-address",
        "jwt-token",
      );
      expect(result).toEqual({ isSuccess: false, error: "Invalid address format" });
    });

```
</issue_to_address>

### Comment 8
<location> `packages/sdk-wrapper/src/__tests__/EncryptionManager.test.ts:239-247` </location>
<code_context>
+  });
+
+  describe("getAuthMessage and getJWT", () => {
+    it("should get auth message for signing", async () => {
+      const mockResult = { message: "Sign this message", error: null };
+      mockKavach.getAuthMessage.mockResolvedValue(mockResult);
+
+      const result = await encryptionManager.getAuthMessage("0x123");
+
+      expect(mockKavach.getAuthMessage).toHaveBeenCalledWith("0x123");
+      expect(result).toEqual(mockResult);
+    });
+
</code_context>

<issue_to_address>
**suggestion (testing):** No test for getAuthMessage with invalid address.

Please add a test case for getAuthMessage using an invalid or empty address to verify proper error handling.

```suggestion
    it("should get auth message for signing", async () => {
      const mockResult = { message: "Sign this message", error: null };
      mockKavach.getAuthMessage.mockResolvedValue(mockResult);

      const result = await encryptionManager.getAuthMessage("0x123");

      expect(mockKavach.getAuthMessage).toHaveBeenCalledWith("0x123");
      expect(result).toEqual(mockResult);
    });

    it("should handle invalid or empty address in getAuthMessage", async () => {
      const mockErrorResult = { message: null, error: "Invalid address" };
      mockKavach.getAuthMessage.mockResolvedValue(mockErrorResult);

      const result = await encryptionManager.getAuthMessage("");

      expect(mockKavach.getAuthMessage).toHaveBeenCalledWith("");
      expect(result).toEqual(mockErrorResult);
    });
```
</issue_to_address>

### Comment 9
<location> `packages/sdk-wrapper/src/__tests__/EncryptionManager.test.ts:249-257` </location>
<code_context>
+      expect(result).toEqual(mockResult);
+    });
+
+    it("should generate JWT from signed message", async () => {
+      const mockResult = "jwt:token";
+      mockKavach.getJWT.mockResolvedValue(mockResult);
+
+      const result = await encryptionManager.getJWT("0x123", "signed-message");
+
+      expect(mockKavach.getJWT).toHaveBeenCalledWith("0x123", "signed-message");
+      expect(result).toEqual(mockResult);
+    });
+  });
</code_context>

<issue_to_address>
**suggestion (testing):** No test for getJWT with invalid signature.

Add a test for getJWT with an invalid or empty signature to ensure proper error handling.

```suggestion
    it("should generate JWT from signed message", async () => {
      const mockResult = "jwt:token";
      mockKavach.getJWT.mockResolvedValue(mockResult);

      const result = await encryptionManager.getJWT("0x123", "signed-message");

      expect(mockKavach.getJWT).toHaveBeenCalledWith("0x123", "signed-message");
      expect(result).toEqual(mockResult);
    });

    it("should handle invalid or empty signature when generating JWT", async () => {
      const mockError = new Error("Invalid signature");
      mockKavach.getJWT.mockRejectedValue(mockError);

      await expect(encryptionManager.getJWT("0x123", "")).rejects.toThrow("Invalid signature");
      expect(mockKavach.getJWT).toHaveBeenCalledWith("0x123", "");
    });
```
</issue_to_address>

### Comment 10
<location> `apps/mcp-server/src/tools/LighthouseGenerateKeyTool.ts:69-73` </location>
<code_context>
    if (params.threshold !== undefined) {
      if (!Number.isInteger(params.threshold) || params.threshold < 2 || params.threshold > 10) {
        return "threshold must be an integer between 2 and 10";
      }
    }

</code_context>

<issue_to_address>
**suggestion (code-quality):** Merge nested if conditions ([`merge-nested-ifs`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/TypeScript/Default-Rules/merge-nested-ifs))

```suggestion
    if (params.threshold !== undefined && (!Number.isInteger(params.threshold) || params.threshold < 2 || params.threshold > 10)) {
          return "threshold must be an integer between 2 and 10";
    }

```

<br/><details><summary>Explanation</summary>Reading deeply nested conditional code is confusing, since you have to keep track of which
conditions relate to which levels. We therefore strive to reduce nesting where
possible, and the situation where two `if` conditions can be combined using
`and` is an easy win.
</details>
</issue_to_address>

### Comment 11
<location> `apps/mcp-server/src/tools/LighthouseGenerateKeyTool.ts:75-79` </location>
<code_context>
    if (params.keyCount !== undefined) {
      if (!Number.isInteger(params.keyCount) || params.keyCount < 3 || params.keyCount > 15) {
        return "keyCount must be an integer between 3 and 15";
      }
    }

</code_context>

<issue_to_address>
**suggestion (code-quality):** Merge nested if conditions ([`merge-nested-ifs`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/TypeScript/Default-Rules/merge-nested-ifs))

```suggestion
    if (params.keyCount !== undefined && (!Number.isInteger(params.keyCount) || params.keyCount < 3 || params.keyCount > 15)) {
          return "keyCount must be an integer between 3 and 15";
    }

```

<br/><details><summary>Explanation</summary>Reading deeply nested conditional code is confusing, since you have to keep track of which
conditions relate to which levels. We therefore strive to reduce nesting where
possible, and the situation where two `if` conditions can be combined using
`and` is an easy win.
</details>
</issue_to_address>

### Comment 12
<location> `packages/sdk-wrapper/src/encryption/EncryptionManager.ts:252-253` </location>
<code_context>
      const result = await kavach.getAuthMessage(address);
      return result;

</code_context>

<issue_to_address>
**suggestion (code-quality):** Inline variable that is immediately returned ([`inline-immediately-returned-variable`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/TypeScript/Default-Rules/inline-immediately-returned-variable))

```suggestion
      return await kavach.getAuthMessage(address);

```

<br/><details><summary>Explanation</summary>Something that we often see in people's code is assigning to a result variable
and then immediately returning it.

Returning the result directly shortens the code and removes an unnecessary
variable, reducing the mental load of reading the function.

Where intermediate variables can be useful is if they then get used as a
parameter or a condition, and the name can act like a comment on what the
variable represents. In the case where you're returning it from a function, the
function name is there to tell you what the result is, so the variable name
is unnecessary.
</details>
</issue_to_address>

### Comment 13
<location> `packages/sdk-wrapper/src/encryption/EncryptionManager.ts:275-276` </location>
<code_context>
      const result = await kavach.getJWT(address, signedMessage);
      return result;

</code_context>

<issue_to_address>
**suggestion (code-quality):** Inline variable that is immediately returned ([`inline-immediately-returned-variable`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/TypeScript/Default-Rules/inline-immediately-returned-variable))

```suggestion
      return await kavach.getJWT(address, signedMessage);

```

<br/><details><summary>Explanation</summary>Something that we often see in people's code is assigning to a result variable
and then immediately returning it.

Returning the result directly shortens the code and removes an unnecessary
variable, reducing the mental load of reading the function.

Where intermediate variables can be useful is if they then get used as a
parameter or a condition, and the name can act like a comment on what the
variable represents. In the case where you're returning it from a function, the
function name is there to tell you what the result is, so the variable name
is unnecessary.
</details>
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +96 to +100
const result = await kavach.shardKey(masterKey, threshold, keyCount);

if (!result || !result.isShardable || !result.keyShards) {
throw new Error("Failed to shard encryption key");
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Error handling for shardKey may be too generic.

Consider using distinct error messages for unshardable keys and missing shards to improve diagnosability.

Suggested change
const result = await kavach.shardKey(masterKey, threshold, keyCount);
if (!result || !result.isShardable || !result.keyShards) {
throw new Error("Failed to shard encryption key");
}
const result = await kavach.shardKey(masterKey, threshold, keyCount);
if (!result) {
throw new Error("No result returned from shardKey");
}
if (!result.isShardable) {
throw new Error("Master key is not shardable");
}
if (!result.keyShards) {
throw new Error("Key shards missing from shardKey result");
}

Comment on lines +136 to +140
const result = await kavach.saveShards(address, cid, authToken, keyShards, sharedTo);

if (!result) {
throw new Error("Failed to save key shards");
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Null result checks may not cover all error cases.

If the SDK provides structured error objects, handle those directly to improve error reporting and propagation.

Suggested change
const result = await kavach.saveShards(address, cid, authToken, keyShards, sharedTo);
if (!result) {
throw new Error("Failed to save key shards");
}
const result = await kavach.saveShards(address, cid, authToken, keyShards, sharedTo);
if (result && result.error) {
// Handle structured error from SDK
throw new Error(`Failed to save key shards: ${result.error.message || JSON.stringify(result.error)}`);
}
if (!result) {
throw new Error("Failed to save key shards: No result returned from SDK");
}

Comment on lines +47 to +48
expect(result).toEqual(mockResult);
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): No test for edge case: threshold > keyCount.

Add a test to ensure an error is thrown when threshold exceeds keyCount, confirming invalid configurations are handled.

Suggested change
expect(result).toEqual(mockResult);
});
expect(result).toEqual(mockResult);
});
it("should throw an error when threshold exceeds keyCount", async () => {
const threshold = 6;
const keyCount = 5;
const errorMessage = "Threshold cannot be greater than keyCount";
mockKavach.generate.mockImplementationOnce(() => {
throw new Error(errorMessage);
});
await expect(encryptionManager.generateKey(threshold, keyCount)).rejects.toThrow(errorMessage);
expect(mockKavach.generate).toHaveBeenCalledWith(threshold, keyCount);
});

});
});

describe("recoverKey", () => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): No test for insufficient key shards during recovery.

Please add a test case that checks recovery with insufficient key shards, ensuring it fails and returns the correct error.

Suggested change
describe("recoverKey", () => {
describe("recoverKey", () => {
it("should fail to recover master key with insufficient shards", async () => {
const mockErrorResult = { masterKey: null, error: "Insufficient shards" };
mockKavach.recoverKey.mockResolvedValue(mockErrorResult);
const insufficientShards: KeyShard[] = [
{ key: "shard1", index: "index1" },
];
const result = await encryptionManager.recoverKey(insufficientShards);
expect(mockKavach.recoverKey).toHaveBeenCalledWith(insufficientShards);
expect(result).toEqual(mockErrorResult);
});

[],
"ADDRESS",
);
expect(result).toEqual({ isSuccess: true, error: null });
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): No test for sharing access to invalid addresses.

Add a test to cover sharing access to an invalid or malformed address, ensuring proper error handling and reporting.

Suggested implementation:

  describe("shareToAddress", () => {
    it("should share access to another address", async () => {
      const mockResult = { isSuccess: true, error: null };
      mockKavach.shareToAddress.mockResolvedValue(mockResult);

      const result = await encryptionManager.shareToAddress(
        "QmTest",
        "0x123",
        "0x456",
        "jwt-token",
      );

      expect(mockKavach.shareToAddress).toHaveBeenCalledWith(
        "QmTest",
        "0x123",
        "0x456",
        "jwt-token",
      );
      expect(result).toEqual({ isSuccess: true, error: null });
    });

    it("should handle error when sharing to an invalid address", async () => {
      const mockErrorResult = { isSuccess: false, error: "Invalid address format" };
      mockKavach.shareToAddress.mockResolvedValue(mockErrorResult);

      const result = await encryptionManager.shareToAddress(
        "QmTest",
        "0x123",
        "invalid-address",
        "jwt-token",
      );

      expect(mockKavach.shareToAddress).toHaveBeenCalledWith(
        "QmTest",
        "0x123",
        "invalid-address",
        "jwt-token",
      );
      expect(result).toEqual({ isSuccess: false, error: "Invalid address format" });
    });

Comment on lines +69 to +73
if (params.threshold !== undefined) {
if (!Number.isInteger(params.threshold) || params.threshold < 2 || params.threshold > 10) {
return "threshold must be an integer between 2 and 10";
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): Merge nested if conditions (merge-nested-ifs)

Suggested change
if (params.threshold !== undefined) {
if (!Number.isInteger(params.threshold) || params.threshold < 2 || params.threshold > 10) {
return "threshold must be an integer between 2 and 10";
}
}
if (params.threshold !== undefined && (!Number.isInteger(params.threshold) || params.threshold < 2 || params.threshold > 10)) {
return "threshold must be an integer between 2 and 10";
}


ExplanationReading deeply nested conditional code is confusing, since you have to keep track of which
conditions relate to which levels. We therefore strive to reduce nesting where
possible, and the situation where two if conditions can be combined using
and is an easy win.

Comment on lines +75 to +79
if (params.keyCount !== undefined) {
if (!Number.isInteger(params.keyCount) || params.keyCount < 3 || params.keyCount > 15) {
return "keyCount must be an integer between 3 and 15";
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): Merge nested if conditions (merge-nested-ifs)

Suggested change
if (params.keyCount !== undefined) {
if (!Number.isInteger(params.keyCount) || params.keyCount < 3 || params.keyCount > 15) {
return "keyCount must be an integer between 3 and 15";
}
}
if (params.keyCount !== undefined && (!Number.isInteger(params.keyCount) || params.keyCount < 3 || params.keyCount > 15)) {
return "keyCount must be an integer between 3 and 15";
}


ExplanationReading deeply nested conditional code is confusing, since you have to keep track of which
conditions relate to which levels. We therefore strive to reduce nesting where
possible, and the situation where two if conditions can be combined using
and is an easy win.

Comment on lines +252 to +253
const result = await kavach.getAuthMessage(address);
return result;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): Inline variable that is immediately returned (inline-immediately-returned-variable)

Suggested change
const result = await kavach.getAuthMessage(address);
return result;
return await kavach.getAuthMessage(address);


ExplanationSomething that we often see in people's code is assigning to a result variable
and then immediately returning it.

Returning the result directly shortens the code and removes an unnecessary
variable, reducing the mental load of reading the function.

Where intermediate variables can be useful is if they then get used as a
parameter or a condition, and the name can act like a comment on what the
variable represents. In the case where you're returning it from a function, the
function name is there to tell you what the result is, so the variable name
is unnecessary.

Comment on lines +275 to +276
const result = await kavach.getJWT(address, signedMessage);
return result;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): Inline variable that is immediately returned (inline-immediately-returned-variable)

Suggested change
const result = await kavach.getJWT(address, signedMessage);
return result;
return await kavach.getJWT(address, signedMessage);


ExplanationSomething that we often see in people's code is assigning to a result variable
and then immediately returning it.

Returning the result directly shortens the code and removes an unnecessary
variable, reducing the mental load of reading the function.

Where intermediate variables can be useful is if they then get used as a
parameter or a condition, and the name can act like a comment on what the
variable represents. In the case where you're returning it from a function, the
function name is there to tell you what the result is, so the variable name
is unnecessary.

@Patrick-Ehimen Patrick-Ehimen merged commit a17179a into main Oct 23, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant