Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: session keys service #268

Merged
merged 1 commit into from
Aug 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/coordinator-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ env:
COORDINATOR_MESSAGE_PROCESS_ZKEY_NAME: "ProcessMessages_10-2-1-2_test"
COORDINATOR_ZKEY_PATH: "./zkeys/"
COORDINATOR_RAPIDSNARK_EXE: "~/rapidsnark/build/prover"
PIMLICO_API_KEY: "pim_"
SUBGRAPH_FOLDER: "./node_modules/maci-subgraph"
SUBGRAPH_NAME: ${{ vars.SUBGRAPH_NAME }}
SUBGRAPH_PROVIDER_URL: ${{ vars.SUBGRAPH_PROVIDER_URL }}
Expand Down
3 changes: 2 additions & 1 deletion packages/coordinator/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,5 @@ SUBGRAPH_DEPLOY_KEY=
# Subgraph project folder
SUBGRAPH_FOLDER=./node_modules/maci-subgraph


# API Key for Pimlico RPC Bundler
PIMLICO_API_KEY="pim_"
2 changes: 1 addition & 1 deletion packages/coordinator/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
zkeys/
proofs/
tally.json

session-keys.json
13 changes: 12 additions & 1 deletion packages/coordinator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,30 +37,38 @@
"@nestjs/websockets": "^10.3.10",
"@nomicfoundation/hardhat-ethers": "^3.0.6",
"@nomicfoundation/hardhat-toolbox": "^5.0.0",
"@zerodev/ecdsa-validator": "^5.3.1",
"@zerodev/permissions": "^5.4.3",
"@zerodev/sdk": "^5.3.8",
"@zerodev/session-key": "^5.4.2",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"dotenv": "^16.4.5",
"ethers": "^6.13.1",
"hardhat": "^2.22.6",
"helmet": "^7.1.0",
"lowdb": "^1.0.0",
0xmad marked this conversation as resolved.
Show resolved Hide resolved
"maci-circuits": "^2.1.0",
"maci-cli": "^2.1.0",
"maci-contracts": "^2.1.0",
"maci-domainobjs": "^2.0.0",
"maci-subgraph": "^2.1.0",
"mustache": "^4.2.0",
"permissionless": "^0.1.44",
"reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1",
"socket.io": "^4.7.5",
"tar": "^7.4.1",
"ts-node": "^10.9.1"
"ts-node": "^10.9.1",
"viem": "^2.7.15"
},
"devDependencies": {
"@nestjs/cli": "^10.4.2",
"@nestjs/schematics": "^10.1.2",
"@nestjs/testing": "^10.3.10",
"@types/express": "^4.17.17",
"@types/jest": "^29.5.2",
"@types/lowdb": "^1.0.15",
"@types/node": "^20.14.11",
"@types/supertest": "^6.0.2",
"fast-check": "^3.20.0",
Expand Down Expand Up @@ -103,6 +111,9 @@
"!<rootDir>/ts/jest/*.js",
"!<rootDir>/hardhat.config.js"
],
"coveragePathIgnorePatterns": [
"<rootDir>/ts/sessionKeys/__tests__/utils.ts"
],
"coverageDirectory": "<rootDir>/coverage",
"testEnvironment": "node"
}
Expand Down
2 changes: 2 additions & 0 deletions packages/coordinator/ts/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ThrottlerModule } from "@nestjs/throttler";
import { CryptoModule } from "./crypto/crypto.module";
import { FileModule } from "./file/file.module";
import { ProofModule } from "./proof/proof.module";
import { SessionKeysModule } from "./sessionKeys/sessionKeys.module";
import { SubgraphModule } from "./subgraph/subgraph.module";

@Module({
Expand All @@ -18,6 +19,7 @@ import { SubgraphModule } from "./subgraph/subgraph.module";
CryptoModule,
SubgraphModule,
ProofModule,
SessionKeysModule,
],
})
export class AppModule {}
17 changes: 17 additions & 0 deletions packages/coordinator/ts/common/__tests__/common.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { genPimlicoRPCUrl } from "../accountAbstraction";
import { ErrorCodes } from "../errors";

describe("common", () => {
describe("genPimlicoRPCUrl", () => {
test("should return the correct RPCUrl", () => {
const rpcUrl = genPimlicoRPCUrl("optimism-sepolia");
expect(rpcUrl).toBeDefined();
expect(rpcUrl).toContain("https://api.pimlico.io/v2/optimism-sepolia/rpc");
});

test("should throw when PIMLICO_API_KEY is not set", () => {
delete process.env.PIMLICO_API_KEY;
expect(() => genPimlicoRPCUrl("optimism-sepolia")).toThrow(ErrorCodes.PIMLICO_API_KEY_NOT_SET);
});
});
});
20 changes: 20 additions & 0 deletions packages/coordinator/ts/common/accountAbstraction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import dotenv from "dotenv";

import { ErrorCodes } from "./errors";

dotenv.config();

/**
* Generate the RPCUrl for Pimlico based on the chain we need to interact with
* @param network - the network we want to interact with
* @returns the RPCUrl for the network
*/
export const genPimlicoRPCUrl = (network: string): string => {
const pimlicoAPIKey = process.env.PIMLICO_API_KEY;

if (!pimlicoAPIKey) {
throw new Error(ErrorCodes.PIMLICO_API_KEY_NOT_SET);
}

return `https://api.pimlico.io/v2/${network}/rpc?apikey=${pimlicoAPIKey}`;
};
3 changes: 3 additions & 0 deletions packages/coordinator/ts/common/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,7 @@ export enum ErrorCodes {
ENCRYPTION = "5",
FILE_NOT_FOUND = "6",
SUBGRAPH_DEPLOY = "7",
SESSION_KEY_NOT_FOUND = "8",
PIMLICO_API_KEY_NOT_SET = "9",
INVALID_APPROVAL = "10",
}
28 changes: 28 additions & 0 deletions packages/coordinator/ts/file/__tests__/file.service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,34 @@ describe("FileService", () => {
jest.clearAllMocks();
});

test("should save session key properly", () => {
const service = new FileService();

const sessionKeyAddress = "0x123";
const sessionKey = "0x456";

service.storeSessionKey(sessionKey, sessionKeyAddress);

const storedSessionKey = service.getSessionKey(sessionKeyAddress);

expect(storedSessionKey).toEqual(sessionKey);
});

test("should delete session key properly", () => {
const service = new FileService();

const sessionKeyAddress = "0x123";
const sessionKey = "0x456";

service.storeSessionKey(sessionKey, sessionKeyAddress);

service.deleteSessionKey(sessionKeyAddress);

const deletedSessionKey = service.getSessionKey(sessionKeyAddress);

expect(deletedSessionKey).toBeUndefined();
});

test("should return public key properly", async () => {
const service = new FileService();

Expand Down
44 changes: 44 additions & 0 deletions packages/coordinator/ts/file/file.service.ts
ctrlc03 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
import { Injectable, Logger } from "@nestjs/common";
import low from "lowdb";
import FileSync from "lowdb/adapters/FileSync";

import fs from "fs";
import path from "path";

import type { IGetPrivateKeyData, IGetPublicKeyData, IGetZkeyFilePathsData } from "./types";
import type { Hex } from "viem";

import { ErrorCodes } from "../common";

/**
* Internal storage structure type.
* named: keys can be queried by name
*/
type TStorage = Record<string, Hex>;

/**
* FileService is responsible for working with local files like:
* 1. RSA public/private keys
Expand All @@ -19,11 +28,46 @@ export class FileService {
*/
private readonly logger: Logger;

/**
* Json file database instance
*/
private db: low.LowdbSync<TStorage>;

/**
* Initialize service
*/
constructor() {
this.logger = new Logger(FileService.name);
this.db = low(new FileSync<TStorage>(path.resolve(__dirname, "..", "..", "./session-keys.json")));
}

/**
* Store session key
*
* @param sessionKey - session key
* @param address - key address
*/
storeSessionKey(sessionKey: Hex, address: string): void {
this.db.set(address, sessionKey).write();
}

/**
* Delete session key
*
* @param address - key address
*/
deleteSessionKey(address: string): void {
this.db.unset(address).write();
}

/**
* Get session key
*
* @param address - key name
* @returns session key
*/
getSessionKey(address: string): Hex | undefined {
return this.db.get(address).value() as Hex | undefined;
}

/**
Expand Down
1 change: 0 additions & 1 deletion packages/coordinator/ts/proof/proof.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ export class ProofGeneratorService {
) {
this.deployment = Deployment.getInstance(hre);
this.deployment.setHre(hre);
this.fileService = fileService;
this.logger = new Logger(ProofGeneratorService.name);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Test } from "@nestjs/testing";
import { zeroAddress } from "viem";

import type { IGenerateSessionKeyReturn } from "../types";

import { SessionKeysController } from "../sessionKeys.controller";
import { SessionKeysService } from "../sessionKeys.service";

describe("SessionKeysController", () => {
let sessionKeysController: SessionKeysController;

const mockSessionKeysService = {
generateSessionKey: jest.fn(),
deactivateSessionKey: jest.fn(),
};

const defaultGenerateSessionKeyReturn: IGenerateSessionKeyReturn = {
sessionKeyAddress: zeroAddress,
};

beforeEach(async () => {
const app = await Test.createTestingModule({
controllers: [SessionKeysController],
})
.useMocker((token) => {
if (token === SessionKeysService) {
mockSessionKeysService.generateSessionKey.mockResolvedValue(defaultGenerateSessionKeyReturn);
return mockSessionKeysService;
}

return jest.fn();
})
.compile();

sessionKeysController = app.get<SessionKeysController>(SessionKeysController);
});

afterEach(() => {
jest.clearAllMocks();
});

describe("v1/session-keys/generate", () => {
test("should return a session key address", async () => {
const data = await sessionKeysController.generateSessionKey();
expect(data).toStrictEqual(defaultGenerateSessionKeyReturn);
});
});

describe("v1/session-keys/delete", () => {
test("should delete a session key", () => {
sessionKeysController.deactivateSessionKey({
sessionKeyAddress: zeroAddress,
});
expect(mockSessionKeysService.deactivateSessionKey).toHaveBeenCalledWith(zeroAddress);
});
});
});
Loading
Loading