Skip to content

Commit

Permalink
Merge pull request #2071 from privacy-scaling-explorations/feature/au…
Browse files Browse the repository at this point in the history
…th-guard

feat(relayer): add auth guard for message publishing
  • Loading branch information
0xmad authored Jan 28, 2025
2 parents 4e37210 + 813bfc7 commit 4adcd53
Show file tree
Hide file tree
Showing 31 changed files with 607 additions and 47 deletions.
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

0 comments on commit 4adcd53

Please sign in to comment.