Skip to content

Commit

Permalink
added helper for creation of package/extracted decryption into job
Browse files Browse the repository at this point in the history
  • Loading branch information
akorchyn committed Apr 8, 2024
1 parent be9262a commit 23ff384
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 23 deletions.
5 changes: 3 additions & 2 deletions relayer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
"description": "NEAR NDC Voting relayer",
"main": "index.js",
"scripts": {
"start:dev": "nodemon",
"start:dev": "nodemon server",
"start:decrypt": "ts-node ./src/index.ts decrypt",
"build": "tsc",
"start:production": "pm2 start dist/index.js --name relayer --update-env",
"start:production": "pm2 start dist/index.js --name relayer --update-env -- server",
"test": "jest"
},
"author": "akorchyn",
Expand Down
11 changes: 5 additions & 6 deletions relayer/src/api/controllers/decryption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,22 @@ export const getPublicKey = async (_: Request, res: Response) => {
return res.status(200).send(base_encode(secretKeys.data.public));
}

export const postDecryption = async (req: Request, res: Response) => {
export const postDecryption = async () => {
const secretKeys = await getSecretKeys();

if (secretKeys.error || !secretKeys.data) {
console.error("Error while getting secret keys", secretKeys.error);
return res.status(500).send({ message: "Error while getting secret keys" });
throw new Error("Error while getting secret keys");
}

if (secretKeys.data.private === undefined) {
const endTime = new Date(secretKeys.data.end_time / 1_000_000).toUTCString()
return res.status(425).send({ message: `Secret key not available yet. Please come after ${endTime} UTC` });
throw new Error(`Secret key not available yet. Please come after ${endTime} UTC`);
}

const encryptedVotes = await getAllVotes();
if (encryptedVotes.length === 0) {
return res.status(400).send({ message: "No votes to decrypt" });
throw new Error(`No votes to decrypt`);
}

const decryptedVotes = new Map<string, VotingPackage>();
Expand Down Expand Up @@ -63,9 +63,8 @@ export const postDecryption = async (req: Request, res: Response) => {
});

if (await sendResultsToContract(Array.from(results.entries()))) {
return res.status(200).send(decryptedVotes);
throw new Error("Error while submitting results to the contract");
}
return res.status(500).send({ message: "Error while submitting results to the contract" });
}

const validateVote = async (vote: VotingPackage, voteNumber: number): Promise<boolean> => {
Expand Down
4 changes: 1 addition & 3 deletions relayer/src/api/routes.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import express from "express";
import { postVote } from "./controllers/submitVote";
import { getPublicKey, postDecryption } from "./controllers/decryption";
import { getPublicKey } from "./controllers/decryption";

const routes = express.Router();

routes.get("/encryption-public-key", getPublicKey);

routes.post("/vote", postVote)
routes.post("/decrypt", postDecryption);

export { routes };
48 changes: 36 additions & 12 deletions relayer/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { app } from "./api/app";

import dotenv from "dotenv";
import { initializeNear } from "./api/utils/near";
import { postDecryption } from "./api/controllers/decryption";

dotenv.config();

Expand All @@ -10,13 +10,32 @@ export const VOTING_CONTRACT = process.env.VOTING_CONTRACT;
export const SNAPSHOT_CONTRACT = process.env.SNAPSHOT_CONTRACT;
export const NETWORK_ID = process.env.NETWORK_ID || 'testnet';
export const RELAYER_ACCOUNT = process.env.RELAYER_ACCOUNT;

export const SECRET_CONTRACT = process.env.SECRET_CONTRACT;
export const SECRET_CODE_HASH = process.env.SECRET_CODE_HASH;

app.listen(PORT, async () => {
let missingEnv = false;
async function startServer() {
app.listen(PORT, () => {
console.log(`Relayer server running on port :${PORT}`);
console.log(`Voting Contract: ${VOTING_CONTRACT}`);
console.log(`Snapshot Contract: ${SNAPSHOT_CONTRACT}`);
console.log(`Network ID: ${NETWORK_ID}`);
console.log(`Relayer Account: ${RELAYER_ACCOUNT}`);
console.log(`Secret Contract: ${SECRET_CONTRACT}`);
console.log(`Secret Code Hash: ${SECRET_CODE_HASH}`);
console.log(`------------------`);
});
}

async function runDecryptionJob() {
console.log("Running decryption job...");

await postDecryption()
}

async function main() {
const mode = process.argv[2];

let missingEnv = false;
if (!VOTING_CONTRACT || !SNAPSHOT_CONTRACT) {
console.error('Please provide VOTING_CONTRACT and SNAPSHOT_CONTRACT in the environment variables');
missingEnv = true;
Expand All @@ -43,12 +62,17 @@ app.listen(PORT, async () => {

await initializeNear();

console.log(`Relayer server running on port :${PORT}`);
console.log(`Voting Contract: ${VOTING_CONTRACT}`);
console.log(`Snapshot Contract: ${SNAPSHOT_CONTRACT}`);
console.log(`Network ID: ${NETWORK_ID}`);
console.log(`Relayer Account: ${RELAYER_ACCOUNT}`);
console.log(`Secret Contract: ${SECRET_CONTRACT}`);
console.log(`Secret Code Hash: ${SECRET_CODE_HASH}`);
console.log(`------------------`);
if (mode === "server") {
await startServer();
} else if (mode === "decrypt") {
await runDecryptionJob();
} else {
console.error("Invalid mode. Please specify 'server' or 'decrypt'.");
process.exit(1);
}
}

main().catch((error) => {
console.error("An error occurred:", error);
process.exit(1);
});
92 changes: 92 additions & 0 deletions relayer/src/utils/vote.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { base_encode } from "near-api-js/lib/utils/serialize";
import { createSignature, encrypt } from "../cryptography";
import { AccountId, EncryptedVotingPackage, EncryptedVotingPackageWithProof, VotingPackage } from "../cryptography/types";

export default class VotingPackageBuilder {
private accountId: AccountId;
private votes: Map<AccountId, number>;

constructor(accountId: AccountId) {
this.accountId = accountId;
this.votes = new Map();
}

addVote(candidate: AccountId, weight: number): VotingPackageBuilder {
this.votes.set(candidate, weight);
return this;
}

removeVote(candidate: AccountId): VotingPackageBuilder {
this.votes.delete(candidate);
return this;
}

/// Builds a voting package with the given encryption key and private key.
/// Private key should follow next standard: 'ed25519:bs58private_key' or 'secp256k1:bs58private_key'
signPackage(privateKey: string): VotingPackageEncryptor {
const votes = Array.from(this.votes.entries()).map(([candidate, weight]) => ({ candidate, weight }));

const signature = createSignature(base_encode(JSON.stringify({ accountId: this.accountId, votes })), privateKey);

if (!signature) {
throw new Error("Failed to sign the voting package");
}

return new VotingPackageEncryptor(privateKey, {
accountId: this.accountId,
votes,
signature,
}, this.accountId);
}
}

class VotingPackageEncryptor {
private privateKey: string;
private vpackage: VotingPackage;
private accountId: AccountId;

constructor(privateKey: string, vpackage: VotingPackage, accountId: AccountId) {
this.privateKey = privateKey;
this.vpackage = vpackage;
this.accountId = accountId;
}

/// Encrypts the voting package with the user provided private key.
/// The encryption key should be secp256k1 private key.
async encryptPackage(encryptionKey: Uint8Array, secretPubKey: Uint8Array): Promise<EncryptedVotingPackageSigner> {
const encryptedData = await encrypt(this.vpackage, encryptionKey, secretPubKey);
if (encryptedData.error || !encryptedData.data) {
throw new Error(`Failed to encrypt the voting package: ${encryptedData.error}`);
}

return new EncryptedVotingPackageSigner(this.privateKey, encryptedData.data, this.accountId);
}

}

class EncryptedVotingPackageSigner {
private privateKey: string;
private vpackage: EncryptedVotingPackage;
private accountId: AccountId;

constructor(privateKey: string, vpackage: EncryptedVotingPackage, accountId: AccountId) {
this.privateKey = privateKey;
this.vpackage = vpackage;
this.accountId = accountId;
}

/// Signs the encrypted voting package
signPackage(): EncryptedVotingPackageWithProof {
const signature = createSignature(this.vpackage.encryptedData + this.vpackage.publicKey, this.privateKey);

if (!signature) {
throw new Error("Failed to sign the encrypted voting package");
}

return {
...this.vpackage,
signature,
accountId: this.accountId,
};
}
}

0 comments on commit 23ff384

Please sign in to comment.