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

fix(coordinator): cleanup session key service and use plaintext approvals #317

Merged
merged 1 commit into from
Sep 9, 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 @@ -14,6 +14,7 @@ env:
COORDINATOR_ZKEY_PATH: "./zkeys/"
COORDINATOR_RAPIDSNARK_EXE: "~/rapidsnark/build/prover"
PIMLICO_API_KEY: "pim_"
RPC_API_KEY: "rpc_"
SUBGRAPH_FOLDER: "./node_modules/maci-subgraph"
SUBGRAPH_NAME: ${{ vars.SUBGRAPH_NAME }}
SUBGRAPH_PROVIDER_URL: ${{ vars.SUBGRAPH_PROVIDER_URL }}
Expand Down
35 changes: 35 additions & 0 deletions packages/coordinator/ts/common/accountAbstraction.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import dotenv from "dotenv";
import { type Chain, createPublicClient, http, type HttpTransport, type PublicClient } from "viem";

import { ErrorCodes } from "./errors";
import { ESupportedNetworks, viemChain } from "./networks";

dotenv.config();

Expand All @@ -18,3 +20,36 @@ export const genPimlicoRPCUrl = (network: string): string => {

return `https://api.pimlico.io/v2/${network}/rpc?apikey=${pimlicoAPIKey}`;
};

/**
* Generate the RPCUrl for Alchemy 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 genAlchemyRPCUrl = (network: ESupportedNetworks): string => {
const rpcAPIKey = process.env.RPC_API_KEY;

if (!rpcAPIKey) {
throw new Error(ErrorCodes.RPC_API_KEY_NOT_SET);
}

switch (network) {
case ESupportedNetworks.OPTIMISM_SEPOLIA:
return `https://opt-sepolia.g.alchemy.com/v2/${rpcAPIKey}`;
case ESupportedNetworks.ETHEREUM_SEPOLIA:
return `https://eth-sepolia.g.alchemy.com/v2/${rpcAPIKey}`;
default:
throw new Error(ErrorCodes.UNSUPPORTED_NETWORK);
}
};

/**
* Get a public client
* @param rpcUrl - the RPC URL
* @returns the public client
*/
export const getPublicClient = (chainName: ESupportedNetworks): PublicClient<HttpTransport, Chain> =>
createPublicClient({
transport: http(genAlchemyRPCUrl(chainName)),
chain: viemChain(chainName),
});
2 changes: 2 additions & 0 deletions packages/coordinator/ts/common/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ export enum ErrorCodes {
SESSION_KEY_NOT_FOUND = "8",
PIMLICO_API_KEY_NOT_SET = "9",
INVALID_APPROVAL = "10",
UNSUPPORTED_NETWORK = "11",
RPC_API_KEY_NOT_SET = "12",
}
110 changes: 65 additions & 45 deletions packages/coordinator/ts/common/networks.ts
Original file line number Diff line number Diff line change
@@ -1,64 +1,84 @@
import {
kittybest marked this conversation as resolved.
Show resolved Hide resolved
arbitrum,
arbitrumSepolia,
base,
baseSepolia,
bsc,
type Chain,
gnosis,
holesky,
linea,
lineaSepolia,
mainnet,
optimism,
optimismSepolia,
polygon,
scroll,
scrollSepolia,
sepolia,
} from "viem/chains";

export enum ESupportedNetworks {
ETHEREUM = "mainnet",
OPTIMISM = "optimism",
OPTIMISM_SEPOLIA = "optimism-sepolia",
BSC = "bsc",
BSC_CHAPEL = "chapel",
GNOSIS_CHAIN = "gnosis",
FUSE = "fuse",
POLYGON = "matic",
FANTOM_OPERA = "fantom",
ZKSYNC_ERA_TESTNET = "zksync-era-testnet",
BOBA = "boba",
MOONBEAM = "moonbeam",
MOONRIVER = "moonriver",
MOONBASE_ALPHA = "mbase",
FANTOM_TESTNET = "fantom-testnet",
ARBITRUM_ONE = "arbitrum-one",
CELO = "celo",
AVALANCHE_FUJI = "fuji",
AVALANCHE = "avalanche",
CELO_ALFAJORES = "celo-alfajores",
HOLESKY = "holesky",
AURORA = "aurora",
AURORA_TESTNET = "aurora-testnet",
HARMONY = "harmony",
LINEA_SEPOLIA = "linea-sepolia",
GNOSIS_CHIADO = "gnosis-chiado",
MODE_SEPOLIA = "mode-sepolia",
MODE = "mode-mainnet",
BASE_SEPOLIA = "base-sepolia",
ZKSYNC_ERA_SEPOLIA = "zksync-era-sepolia",
POLYGON_ZKEVM = "polygon-zkevm",
ZKSYNC_ERA = "zksync-era",
ETHEREUM_SEPOLIA = "sepolia",
ARBITRUM_SEPOLIA = "arbitrum-sepolia",
LINEA = "linea",
BASE = "base",
SCROLL_SEPOLIA = "scroll-sepolia",
SCROLL = "scroll",
BLAST_MAINNET = "blast-mainnet",
ASTAR_ZKEVM_MAINNET = "astar-zkevm-mainnet",
SEI_TESTNET = "sei-testnet",
BLAST_TESTNET = "blast-testnet",
ETHERLINK_TESTNET = "etherlink-testnet",
XLAYER_SEPOLIA = "xlayer-sepolia",
XLAYER_MAINNET = "xlayer-mainnet",
POLYGON_AMOY = "polygon-amoy",
ZKYOTO_TESTNET = "zkyoto-testnet",
POLYGON_ZKEVM_CARDONA = "polygon-zkevm-cardona",
SEI_MAINNET = "sei-mainnet",
ROOTSTOCK_MAINNET = "rootstock",
IOTEX_MAINNET = "iotex",
NEAR_MAINNET = "near-mainnet",
NEAR_TESTNET = "near-testnet",
COSMOS = "cosmoshub-4",
COSMOS_HUB = "theta-testnet-001",
OSMOSIS = "osmosis-1",
OSMO_TESTNET = "osmo-test-4",
ARWEAVE = "arweave-mainnet",
BITCOIN = "btc",
SOLANA = "solana-mainnet-beta",
INJECTIVE_MAINNET = "injective-mainnet",
INJECTIVE_TESTNET = "injective-testnet",
}

/**
* Get the Viem chain for a given network
*
* @param network - the network to get the chain for
* @returns the Viem chain
*/
export const viemChain = (network: ESupportedNetworks): Chain => {
switch (network) {
case ESupportedNetworks.ETHEREUM:
return mainnet;
case ESupportedNetworks.ETHEREUM_SEPOLIA:
return sepolia;
case ESupportedNetworks.ARBITRUM_ONE:
return arbitrum;
case ESupportedNetworks.ARBITRUM_SEPOLIA:
return arbitrumSepolia;
case ESupportedNetworks.BASE_SEPOLIA:
return baseSepolia;
case ESupportedNetworks.LINEA_SEPOLIA:
return lineaSepolia;
case ESupportedNetworks.SCROLL_SEPOLIA:
return scrollSepolia;
case ESupportedNetworks.SCROLL:
return scroll;
case ESupportedNetworks.BASE:
return base;
case ESupportedNetworks.HOLESKY:
return holesky;
case ESupportedNetworks.LINEA:
return linea;
case ESupportedNetworks.BSC:
return bsc;
case ESupportedNetworks.GNOSIS_CHAIN:
return gnosis;
case ESupportedNetworks.POLYGON:
return polygon;
case ESupportedNetworks.OPTIMISM:
return optimism;
case ESupportedNetworks.OPTIMISM_SEPOLIA:
return optimismSepolia;
default:
throw new Error(`Unsupported network: ${network}`);
}
};
2 changes: 1 addition & 1 deletion packages/coordinator/ts/file/file.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export class FileService {
*/
constructor() {
this.logger = new Logger(FileService.name);
this.db = low(new FileSync<TStorage>(path.resolve(__dirname, "..", "..", "./session-keys.json")));
this.db = low(new FileSync<TStorage>(path.resolve(process.cwd(), "./session-keys.json")));
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import dotenv from "dotenv";
import { ZeroAddress } from "ethers";
import { zeroAddress } from "viem";
import { optimismSepolia } from "viem/chains";

import { KeyLike } from "crypto";

import { ErrorCodes } from "../../common";
import { CryptoService } from "../../crypto/crypto.service";
import { ErrorCodes, ESupportedNetworks } from "../../common";
import { FileService } from "../../file/file.service";
import { SessionKeysService } from "../sessionKeys.service";

Expand All @@ -15,21 +10,18 @@ import { mockSessionKeyApproval } from "./utils";
dotenv.config();

describe("SessionKeysService", () => {
const cryptoService = new CryptoService();
const fileService = new FileService();
let sessionKeysService: SessionKeysService;
let publicKey: KeyLike;

beforeAll(async () => {
publicKey = (await fileService.getPublicKey()).publicKey;
sessionKeysService = new SessionKeysService(cryptoService, fileService);
beforeAll(() => {
sessionKeysService = new SessionKeysService(fileService);
});

describe("generateSessionKey", () => {
test("should generate and store a session key", () => {
const sessionKeyAddress = sessionKeysService.generateSessionKey();
expect(sessionKeyAddress).toBeDefined();
expect(sessionKeyAddress).not.toEqual(ZeroAddress);
expect(sessionKeyAddress).not.toEqual(zeroAddress);

const sessionKey = fileService.getSessionKey(sessionKeyAddress.sessionKeyAddress);
expect(sessionKey).toBeDefined();
Expand All @@ -40,7 +32,7 @@ describe("SessionKeysService", () => {
test("should delete a session key", () => {
const sessionKeyAddress = sessionKeysService.generateSessionKey();
expect(sessionKeyAddress).toBeDefined();
expect(sessionKeyAddress).not.toEqual(ZeroAddress);
expect(sessionKeyAddress).not.toEqual(zeroAddress);

const sessionKey = fileService.getSessionKey(sessionKeyAddress.sessionKeyAddress);
expect(sessionKey).toBeDefined();
Expand All @@ -54,22 +46,19 @@ describe("SessionKeysService", () => {
describe("generateClientFromSessionKey", () => {
test("should fail to generate a client with an invalid approval", async () => {
const sessionKeyAddress = sessionKeysService.generateSessionKey();
const approval = await mockSessionKeyApproval(sessionKeyAddress.sessionKeyAddress);
const encryptedApproval = cryptoService.encrypt(publicKey, approval);
await expect(
sessionKeysService.generateClientFromSessionKey(
sessionKeyAddress.sessionKeyAddress,
encryptedApproval,
optimismSepolia,
"0xinvalid",
ESupportedNetworks.OPTIMISM_SEPOLIA,
),
).rejects.toThrow(ErrorCodes.INVALID_APPROVAL);
});

test("should throw when given a non existent session key address", async () => {
const approval = await mockSessionKeyApproval(zeroAddress);
const encryptedApproval = cryptoService.encrypt(publicKey, approval);
await expect(
sessionKeysService.generateClientFromSessionKey(zeroAddress, encryptedApproval, optimismSepolia),
sessionKeysService.generateClientFromSessionKey(zeroAddress, approval, ESupportedNetworks.OPTIMISM_SEPOLIA),
).rejects.toThrow(ErrorCodes.SESSION_KEY_NOT_FOUND);
});

Expand All @@ -86,17 +75,16 @@ describe("SessionKeysService", () => {

const sessionKeyAddress = sessionKeysService.generateSessionKey();
const approval = await mockSessionKeyApproval(sessionKeyAddress.sessionKeyAddress);
const encryptedApproval = cryptoService.encrypt(publicKey, approval);

const client = await sessionKeysService.generateClientFromSessionKey(
sessionKeyAddress.sessionKeyAddress,
encryptedApproval,
optimismSepolia,
approval,
ESupportedNetworks.OPTIMISM_SEPOLIA,
);
expect(mockGenerateClientFromSessionKey).toHaveBeenCalledWith(
sessionKeyAddress.sessionKeyAddress,
encryptedApproval,
optimismSepolia,
approval,
ESupportedNetworks.OPTIMISM_SEPOLIA,
);
expect(client).toEqual({ mockedClient: true });
});
Expand Down
7 changes: 4 additions & 3 deletions packages/coordinator/ts/sessionKeys/sessionKeys.module.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { Module } from "@nestjs/common";

import { CryptoModule } from "../crypto/crypto.module";
import { CryptoService } from "../crypto/crypto.service";
import { FileModule } from "../file/file.module";

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

@Module({
imports: [FileModule, CryptoModule],
imports: [FileModule],
controllers: [SessionKeysController],
providers: [SessionKeysService],
providers: [SessionKeysService, CryptoService],
exports: [SessionKeysService],
})
export class SessionKeysModule {}
Loading
Loading