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(relayer): add auth guard for message publishing #2071

Merged
merged 1 commit into from
Jan 28, 2025
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
52 changes: 52 additions & 0 deletions .github/workflows/relayer-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ env:
TTL: ${{ vars.RELAYER_TTL }}
LIMIT: ${{ vars.RELAYER_LIMIT }}
ALLOWED_ORIGINS: ${{ vars.ALLOWED_ORIGINS }}
MAX_MESSAGES: ${{ vars.RELAYER_MAX_MESSAGES || 20 }}
MONGO_DB_URI: ${{ secrets.RELAYER_MONGO_DB_URI }}
MONGODB_USER: ${{ secrets.MONGODB_USER }}
MONGODB_PASSWORD: ${{ secrets.MONGODB_PASSWORD }}
Expand All @@ -36,6 +37,34 @@ jobs:
node-version: 20
cache: "pnpm"

- name: Get changed files
id: get-changed-files
uses: jitterbit/get-changed-files@v1
with:
format: "csv"

- name: Check for changes in 'circuit' folder
id: check_changes
run: |
CHANGED_FILES=${{ steps.get-changed-files.outputs.all }}
if echo "$CHANGED_FILES" | grep -q "\.circom"; then
echo "CHANGED=true" >> $GITHUB_ENV
echo "Circuits have changes."
else
echo "CHANGED=false" >> $GITHUB_ENV
echo "No changes on circuits."
fi

- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install --yes \
build-essential \
libgmp-dev \
libsodium-dev \
nasm \
nlohmann-json3-dev

- name: Install
run: |
pnpm install --frozen-lockfile --prefer-offline
Expand All @@ -44,6 +73,29 @@ jobs:
run: |
pnpm run build

- name: Download rapidsnark (1c137)
run: |
mkdir -p ~/rapidsnark/build
wget -qO ~/rapidsnark/build/prover https://maci-devops-zkeys.s3.ap-northeast-2.amazonaws.com/rapidsnark-linux-amd64-1c137
chmod +x ~/rapidsnark/build/prover

- name: Download circom Binary v2.1.6
run: |
wget -qO ${{ github.workspace }}/circom https://github.com/iden3/circom/releases/download/v2.1.6/circom-linux-amd64
chmod +x ${{ github.workspace }}/circom
sudo mv ${{ github.workspace }}/circom /bin/circom

- name: Compile Circuits And Generate zkeys
if: ${{ env.CHANGED == 'true' }}
run: |
pnpm build:circuits-c -- --outPath ../../apps/relayer/zkeys
pnpm setup:zkeys -- --outPath ../../apps/relayer/zkeys

- name: Download zkeys
if: ${{ env.CHANGED == 'false' }}
run: |
pnpm download-zkeys:test:relayer

- name: Run hardhat
run: |
pnpm run hardhat &
Expand Down
2 changes: 2 additions & 0 deletions apps/relayer/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,5 @@ PORT=

# Mnemonic phrase
MNEMONIC=""

MAX_MESSAGES=20
3 changes: 3 additions & 0 deletions apps/relayer/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
build/
coverage/
.env
zkeys
deploy-config.json
deployed-contracts.json
2 changes: 2 additions & 0 deletions apps/relayer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@
"helia": "^5.2.0",
"helmet": "^8.0.0",
"lodash": "^4.17.21",
"maci-circuits": "workspace:^2.5.0",
"maci-cli": "workspace:^2.5.0",
"maci-contracts": "workspace:^2.5.0",
"maci-domainobjs": "workspace:^2.5.0",
"mongoose": "^8.9.5",
Expand Down
38 changes: 38 additions & 0 deletions apps/relayer/tests/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { homedir } from "os";
import path from "path";
import url from "url";

export const STATE_TREE_DEPTH = 10;
export const INT_STATE_TREE_DEPTH = 1;
export const VOTE_OPTION_TREE_DEPTH = 2;
export const MESSAGE_BATCH_SIZE = 20;

export const dirname = url.fileURLToPath(new URL(".", import.meta.url));

export const pollJoiningZkey = path.resolve(dirname, "../zkeys/PollJoining_10_test/PollJoining_10_test.0.zkey");
export const pollJoinedZkey = path.resolve(dirname, "../zkeys/PollJoined_10_test/PollJoined_10_test.0.zkey");
export const pollWasm = path.resolve(
dirname,
"../zkeys/PollJoining_10_test/PollJoining_10_test_js/PollJoining_10_test.wasm",
);
export const pollWitgen = path.resolve(
dirname,
"../zkeys/PollJoining_10_test/PollJoining_10_test_cpp/PollJoining_10_test",
);
export const pollJoinedWasm = path.resolve(
dirname,
"../zkeys/PollJoined_10_test/PollJoined_10_test_js/PollJoined_10_test.wasm",
);
export const pollJoinedWitgen = path.resolve(
dirname,
"../zkeys/PollJoined_10_test/PollJoined_10_test_cpp/PollJoined_10_test",
);
export const rapidsnark = `${homedir()}/rapidsnark/build/prover`;
export const processMessagesZkeyPathNonQv = path.resolve(
dirname,
"../zkeys/ProcessMessagesNonQv_10-20-2_test/ProcessMessagesNonQv_10-20-2_test.0.zkey",
);
export const tallyVotesZkeyPathNonQv = path.resolve(
dirname,
"../zkeys/TallyVotesNonQv_10-1-2_test/TallyVotesNonQv_10-1-2_test.0.zkey",
);
159 changes: 156 additions & 3 deletions apps/relayer/tests/messages.test.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,113 @@
import { jest } from "@jest/globals";
import { HttpStatus, ValidationPipe, type INestApplication } from "@nestjs/common";
import { Test } from "@nestjs/testing";
import { ZeroAddress } from "ethers";
import hardhat from "hardhat";
import { genProof } from "maci-circuits";
import { deploy, deployPoll, deployVkRegistryContract, joinPoll, setVerifyingKeys, signup } from "maci-cli";
import { formatProofForVerifierContract, genMaciStateFromContract } from "maci-contracts";
import { Keypair } from "maci-domainobjs";
import request from "supertest";

import type { App } from "supertest/types";

import { AppModule } from "../ts/app.module";

import {
INT_STATE_TREE_DEPTH,
MESSAGE_BATCH_SIZE,
STATE_TREE_DEPTH,
VOTE_OPTION_TREE_DEPTH,
pollJoinedZkey,
pollJoiningZkey,
processMessagesZkeyPathNonQv,
tallyVotesZkeyPathNonQv,
pollWasm,
pollWitgen,
rapidsnark,
pollJoinedWitgen,
pollJoinedWasm,
} from "./constants";

jest.unmock("maci-contracts/typechain-types");

describe("Integration messages", () => {
let app: INestApplication;
let circuitInputs: Record<string, string>;
let stateLeafIndex: number;
let maciContractAddress: string;

const coordinatorKeypair = new Keypair();
const user = new Keypair();

beforeAll(async () => {
const [signer] = await hardhat.ethers.getSigners();

const vkRegistry = await deployVkRegistryContract({ signer });
await setVerifyingKeys({
quiet: true,
vkRegistry,
stateTreeDepth: STATE_TREE_DEPTH,
intStateTreeDepth: INT_STATE_TREE_DEPTH,
voteOptionTreeDepth: VOTE_OPTION_TREE_DEPTH,
messageBatchSize: MESSAGE_BATCH_SIZE,
processMessagesZkeyPathNonQv,
tallyVotesZkeyPathNonQv,
pollJoiningZkeyPath: pollJoiningZkey,
pollJoinedZkeyPath: pollJoinedZkey,
useQuadraticVoting: false,
signer,
});

const maciAddresses = await deploy({ stateTreeDepth: 10, signer });

maciContractAddress = maciAddresses.maciAddress;

await deployPoll({
pollDuration: 30,
intStateTreeDepth: INT_STATE_TREE_DEPTH,
messageBatchSize: MESSAGE_BATCH_SIZE,
voteOptionTreeDepth: VOTE_OPTION_TREE_DEPTH,
coordinatorPubkey: coordinatorKeypair.pubKey.serialize(),
useQuadraticVoting: false,
signer,
});

await signup({ maciAddress: maciAddresses.maciAddress, maciPubKey: user.pubKey.serialize(), signer });

const { pollStateIndex, timestamp, voiceCredits } = await joinPoll({
maciAddress: maciAddresses.maciAddress,
pollId: 0n,
privateKey: user.privKey.serialize(),
stateIndex: 1n,
pollJoiningZkey,
pollWasm,
pollWitgen,
rapidsnark,
signer,
useWasm: true,
quiet: true,
});

const maciState = await genMaciStateFromContract(
signer.provider,
maciAddresses.maciAddress,
coordinatorKeypair,
0n,
);

const poll = maciState.polls.get(0n);

poll!.updatePoll(BigInt(maciState.pubKeys.length));

stateLeafIndex = Number(pollStateIndex);

circuitInputs = poll!.joinedCircuitInputs({
maciPrivKey: user.privKey,
stateLeafIndex: BigInt(pollStateIndex),
voiceCreditsBalance: BigInt(voiceCredits),
joinTimestamp: BigInt(timestamp),
}) as unknown as typeof circuitInputs;

const moduleFixture = await Test.createTestingModule({
imports: [AppModule],
}).compile();
Expand All @@ -29,8 +125,9 @@ describe("Integration messages", () => {
const keypair = new Keypair();

const defaultSaveMessagesArgs = {
maciContractAddress: ZeroAddress,
maciContractAddress,
poll: 0,
stateLeafIndex,
messages: [
{
data: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"],
Expand All @@ -39,10 +136,40 @@ describe("Integration messages", () => {
],
};

test("should throw an error if there is no valid proof", async () => {
const result = await request(app.getHttpServer() as App)
.post("/v1/messages/publish")
.send({
...defaultSaveMessagesArgs,
maciContractAddress,
stateLeafIndex,
poll: 0,
proof: ["0", "0", "0", "0", "0", "0", "0", "0"],
})
.expect(HttpStatus.FORBIDDEN);

expect(result.body).toStrictEqual({
error: "Forbidden",
statusCode: HttpStatus.FORBIDDEN,
message: "Forbidden resource",
});
});

test("should throw an error if dto is invalid", async () => {
const { proof } = await genProof({
inputs: circuitInputs,
zkeyPath: pollJoinedZkey,
useWasm: true,
rapidsnarkExePath: rapidsnark,
witnessExePath: pollJoinedWitgen,
wasmPath: pollJoinedWasm,
});

const result = await request(app.getHttpServer() as App)
.post("/v1/messages/publish")
.send({
stateLeafIndex,
proof: formatProofForVerifierContract(proof),
maciContractAddress: "invalid",
poll: "-1",
messages: [],
Expand All @@ -62,10 +189,22 @@ describe("Integration messages", () => {
});

test("should throw an error if messages dto is invalid", async () => {
const { proof } = await genProof({
inputs: circuitInputs,
zkeyPath: pollJoinedZkey,
useWasm: true,
rapidsnarkExePath: rapidsnark,
witnessExePath: pollJoinedWitgen,
wasmPath: pollJoinedWasm,
});

const result = await request(app.getHttpServer() as App)
.post("/v1/messages/publish")
.send({
...defaultSaveMessagesArgs,
maciContractAddress,
stateLeafIndex,
proof: formatProofForVerifierContract(proof),
messages: [{ data: [], publicKey: "invalid" }],
})
.expect(HttpStatus.BAD_REQUEST);
Expand All @@ -78,9 +217,23 @@ describe("Integration messages", () => {
});

test("should publish user messages properly", async () => {
const { proof } = await genProof({
inputs: circuitInputs,
zkeyPath: pollJoinedZkey,
useWasm: true,
rapidsnarkExePath: rapidsnark,
witnessExePath: pollJoinedWitgen,
wasmPath: pollJoinedWasm,
});

const result = await request(app.getHttpServer() as App)
.post("/v1/messages/publish")
.send(defaultSaveMessagesArgs)
.send({
...defaultSaveMessagesArgs,
maciContractAddress,
stateLeafIndex,
proof: formatProofForVerifierContract(proof),
})
.expect(HttpStatus.CREATED);

expect(result.status).toBe(HttpStatus.CREATED);
Expand Down
Loading
Loading