diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 1051526e4e..97c2256d4f 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -6,6 +6,11 @@ on: jobs: generate-proving-keys: + strategy: + fail-fast: false + matrix: + command: ["test:cli", "test:integration"] + runs-on: ubuntu-22.04 steps: @@ -34,10 +39,9 @@ jobs: npm run bootstrap npm run build - - name: Compile Contracts + - name: Run hardhat fork run: | cd contracts - npm run compileSol npm run hardhat & - name: Download rapidsnark (1c137) @@ -60,15 +64,8 @@ jobs: npx zkey-manager compile -c ./zkeys.config.yml npx zkey-manager genZkeys -c ./zkeys.config.yml - - name: e2e Tests - run: | - cd cli - npm run test - - - name: Integration Tests - run: | - cd integrationTests - npm run test + - name: ${{ matrix.command }} + run: npm run ${{ matrix.command }} - name: Stop Hardhat run: kill $(lsof -t -i:8545) diff --git a/.github/workflows/reusable-e2e.yml b/.github/workflows/reusable-e2e.yml index c110d18a2d..7e8c6df464 100644 --- a/.github/workflows/reusable-e2e.yml +++ b/.github/workflows/reusable-e2e.yml @@ -55,7 +55,7 @@ jobs: npm run bootstrap npm run build - - name: Compile Contracts + - name: Run hardhat fork run: | cd contracts npm run hardhat & diff --git a/core/ts/MaciState.ts b/core/ts/MaciState.ts index 6aec2579d7..21635308e7 100644 --- a/core/ts/MaciState.ts +++ b/core/ts/MaciState.ts @@ -149,7 +149,7 @@ class MaciState implements IMaciState { const maciState = new MaciState(json.stateTreeDepth); // assign the json values to the new instance - maciState.stateLeaves = json.stateLeaves.map((leaf: string) => StateLeaf.fromJSON(leaf)); + maciState.stateLeaves = json.stateLeaves.map((leaf) => StateLeaf.fromJSON(leaf)); maciState.pollBeingProcessed = json.pollBeingProcessed; maciState.currentPollBeingProcessed = json.currentPollBeingProcessed; maciState.numSignUps = json.numSignUps; diff --git a/core/ts/Poll.ts b/core/ts/Poll.ts index 5184e16dd7..3fdef4f0e5 100644 --- a/core/ts/Poll.ts +++ b/core/ts/Poll.ts @@ -11,7 +11,7 @@ import { stringifyBigInts, genTreeCommitment, } from "maci-crypto"; -import { PubKey, Command, PCommand, TCommand, Message, Keypair, StateLeaf, Ballot, PrivKey } from "maci-domainobjs"; +import { PubKey, ICommand, PCommand, TCommand, Message, Keypair, StateLeaf, Ballot, PrivKey } from "maci-domainobjs"; import { MaciState } from "./MaciState"; import { TreeDepths, MaxValues, BatchSizes, packTallyVotesSmallVals, packSubsidySmallVals } from "./utils/utils"; @@ -73,7 +73,7 @@ class Poll implements IPoll { public messages: Message[] = []; public messageTree: IncrementalQuinTree; - public commands: Command[] = []; + public commands: ICommand[] = []; public encPubKeys: PubKey[] = []; @@ -312,7 +312,7 @@ class Poll implements IPoll { this.messageTree.insert(messageLeaf); const command = new TCommand(_message.data[0], _message.data[1], BigInt(this.pollId)); - this.commands.push(command); + this.commands.push(command as ICommand); }; /* @@ -338,12 +338,12 @@ class Poll implements IPoll { const sharedKey = Keypair.genEcdhSharedKey(this.coordinatorKeypair.privKey, _encPubKey); try { const { command } = PCommand.decrypt(_message, sharedKey); - this.commands.push(command); + this.commands.push(command as ICommand); } catch (e) { //console.log(`error cannot decrypt: ${e.message}`) const keyPair = new Keypair(); const command = new PCommand(BigInt(0), keyPair.pubKey, BigInt(0), BigInt(0), BigInt(0), BigInt(0), BigInt(0)); - this.commands.push(command); + this.commands.push(command as ICommand); } }; @@ -1054,19 +1054,19 @@ class Poll implements IPoll { this.stateTreeDepth, ); - copied.stateLeaves = this.stateLeaves.map((x: StateLeaf) => x.copy()); - copied.messages = this.messages.map((x: Message) => x.copy()); - copied.commands = this.commands.map((x: Command) => x.copy()); - copied.ballots = this.ballots.map((x: Ballot) => x.copy()); - copied.encPubKeys = this.encPubKeys.map((x: PubKey) => x.copy()); + copied.stateLeaves = this.stateLeaves.map((x) => x.copy()); + copied.messages = this.messages.map((x) => x.copy()); + copied.commands = this.commands.map((x) => x.copy()); + copied.ballots = this.ballots.map((x) => x.copy()); + copied.encPubKeys = this.encPubKeys.map((x) => x.copy()); if (this.ballotTree) { copied.ballotTree = this.ballotTree.copy(); } copied.currentMessageBatchIndex = this.currentMessageBatchIndex; copied.maciStateRef = this.maciStateRef; copied.messageTree = this.messageTree.copy(); - copied.results = this.results.map((x: bigint) => BigInt(x.toString())); - copied.perVOSpentVoiceCredits = this.perVOSpentVoiceCredits.map((x: bigint) => BigInt(x.toString())); + copied.results = this.results.map((x) => BigInt(x.toString())); + copied.perVOSpentVoiceCredits = this.perVOSpentVoiceCredits.map((x) => BigInt(x.toString())); copied.numBatchesProcessed = Number(this.numBatchesProcessed.toString()); copied.numBatchesTallied = Number(this.numBatchesTallied.toString()); @@ -1177,8 +1177,8 @@ class Poll implements IPoll { // set all properties poll.ballots = json.ballots.map((ballot: Ballot) => Ballot.fromJSON(ballot)); poll.encPubKeys = json.encPubKeys.map((key: string) => PubKey.deserialize(key)); - poll.messages = json.messages.map((message: Message) => Message.fromJSON(message)); - poll.commands = json.commands.map((command: any) => { + poll.messages = json.messages.map((message) => Message.fromJSON(message)); + poll.commands = json.commands.map((command) => { switch (command.cmdType) { case "1": { return PCommand.fromJSON(command); @@ -1187,7 +1187,7 @@ class Poll implements IPoll { return TCommand.fromJSON(command); } default: { - return Command.fromJSON(command); + return { cmdType: command.cmdType }; } } }); diff --git a/domainobjs/.eslintrc.js b/domainobjs/.eslintrc.js new file mode 100644 index 0000000000..a22d4fb52c --- /dev/null +++ b/domainobjs/.eslintrc.js @@ -0,0 +1,107 @@ +const fs = require("fs"); +const path = require("path"); + +const prettierConfig = fs.readFileSync(path.resolve(__dirname, "../.prettierrc"), "utf8"); +const prettierOptions = JSON.parse(prettierConfig); +const isProduction = process.env.NODE_ENV === "production"; + +module.exports = { + root: true, + extends: [ + "airbnb", + "prettier", + "plugin:import/recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended", + "plugin:@typescript-eslint/recommended-type-checked", + "plugin:@typescript-eslint/strict", + "plugin:@typescript-eslint/strict-type-checked", + "plugin:@typescript-eslint/stylistic", + "plugin:@typescript-eslint/stylistic-type-checked", + "plugin:import/typescript", + ], + plugins: ["json", "prettier", "unused-imports", "import", "@typescript-eslint"], + parser: "@typescript-eslint/parser", + env: { + node: true, + mocha: true, + es2022: true, + }, + settings: { + react: { + version: "999.999.999", + }, + "import/resolver": { + typescript: {}, + node: { + extensions: [".ts", ".js"], + moduleDirectory: ["node_modules", "ts", "src"], + }, + }, + }, + parserOptions: { + project: path.resolve(__dirname, "./tsconfig.json"), + sourceType: "module", + typescript: true, + ecmaVersion: 2022, + experimentalDecorators: true, + requireConfigFile: false, + ecmaFeatures: { + classes: true, + impliedStrict: true, + }, + warnOnUnsupportedTypeScriptVersion: true, + }, + reportUnusedDisableDirectives: isProduction, + rules: { + "import/no-cycle": ["error"], + "unused-imports/no-unused-imports": "error", + "import/no-extraneous-dependencies": [ + "error", + { + devDependencies: ["**/*.test.ts"], + }, + ], + "no-debugger": isProduction ? "error" : "off", + "no-console": "error", + "no-underscore-dangle": "error", + "no-redeclare": ["error", { builtinGlobals: true }], + "import/order": [ + "error", + { + groups: ["external", "builtin", "internal", "type", "parent", "sibling", "index", "object"], + alphabetize: { + order: "asc", + caseInsensitive: true, + }, + warnOnUnassignedImports: true, + "newlines-between": "always", + }, + ], + "prettier/prettier": ["error", prettierOptions], + "import/prefer-default-export": "off", + "import/extensions": ["error", { json: "always" }], + "class-methods-use-this": "off", + "prefer-promise-reject-errors": "off", + "max-classes-per-file": "off", + "no-use-before-define": ["off"], + "no-shadow": "off", + curly: ["error", "all"], + + "@typescript-eslint/explicit-member-accessibility": ["error", { accessibility: "no-public" }], + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/prefer-nullish-coalescing": "off", + "@typescript-eslint/no-floating-promises": "off", + "@typescript-eslint/no-explicit-any": "error", + "@typescript-eslint/explicit-module-boundary-types": "error", + "@typescript-eslint/no-use-before-define": ["error", { functions: false, classes: false }], + "@typescript-eslint/no-misused-promises": ["error", { checksVoidReturn: false }], + "@typescript-eslint/no-shadow": [ + "error", + { + builtinGlobals: true, + allow: ["location", "event", "history", "name", "status", "Option", "test", "expect"], + }, + ], + }, +}; diff --git a/domainobjs/package.json b/domainobjs/package.json index 4cc6990743..fa910d94f6 100644 --- a/domainobjs/package.json +++ b/domainobjs/package.json @@ -2,7 +2,7 @@ "name": "maci-domainobjs", "version": "1.1.2", "description": "", - "main": "build/index.js", + "main": "build/ts/index.js", "scripts": { "watch": "tsc --watch", "build": "tsc", diff --git a/domainobjs/ts/__tests__/ballot.test.ts b/domainobjs/ts/__tests__/ballot.test.ts index b50760babb..9483d9a3bf 100644 --- a/domainobjs/ts/__tests__/ballot.test.ts +++ b/domainobjs/ts/__tests__/ballot.test.ts @@ -1,17 +1,18 @@ -import { Ballot } from ".."; import { expect } from "chai"; +import { Ballot } from ".."; + describe("Ballot", () => { it("should create a new ballot and hash it", () => { const b = new Ballot(0, 2); const h = b.hash(); - expect(h).to.not.be.null; + expect(h).to.not.eq(null); }); it("copy should produce a deep copy", () => { const b1 = Ballot.genRandomBallot(2, 2); const b2 = b1.copy(); - expect(b1.equals(b2)).to.be.true; + expect(b1.equals(b2)).to.eq(true); }); it("asCircuitInputs should produce an array", () => { diff --git a/domainobjs/ts/__tests__/commands.test.ts b/domainobjs/ts/__tests__/commands.test.ts index c886d8a0bc..531a57b457 100644 --- a/domainobjs/ts/__tests__/commands.test.ts +++ b/domainobjs/ts/__tests__/commands.test.ts @@ -1,6 +1,7 @@ -import { Keypair, Message, PCommand, TCommand } from "../"; -import { genRandomSalt } from "maci-crypto"; import { expect } from "chai"; +import { genRandomSalt } from "maci-crypto"; + +import { Keypair, Message, PCommand, TCommand } from ".."; describe("Commands & Messages", () => { const { privKey, pubKey } = new Keypair(); @@ -11,9 +12,8 @@ describe("Commands & Messages", () => { const newPubKey = k.pubKey; const ecdhSharedKey = Keypair.genEcdhSharedKey(privKey, pubKey1); - const random50bitBigInt = (): bigint => { - return ((BigInt(1) << BigInt(50)) - BigInt(1)) & BigInt(genRandomSalt().toString()); - }; + // eslint-disable-next-line no-bitwise + const random50bitBigInt = (): bigint => ((BigInt(1) << BigInt(50)) - BigInt(1)) & BigInt(genRandomSalt().toString()); const command: PCommand = new PCommand( random50bitBigInt(), newPubKey, @@ -21,18 +21,18 @@ describe("Commands & Messages", () => { random50bitBigInt(), random50bitBigInt(), random50bitBigInt(), - genRandomSalt() as bigint, + genRandomSalt(), ); const signature = command.sign(privKey); const message = command.encrypt(signature, ecdhSharedKey); const decrypted = PCommand.decrypt(message, ecdhSharedKey); it("command.sign() should produce a valid signature", () => { - expect(command.verifySignature(signature, pubKey)).to.be.true; + expect(command.verifySignature(signature, pubKey)).to.eq(true); }); it("decrypted message should match the original command", () => { - expect(decrypted.command.equals(command)).to.be.true; + expect(decrypted.command.equals(command)).to.eq(true); expect(decrypted.signature.R8[0].toString()).to.eq(signature.R8[0].toString()); expect(decrypted.signature.R8[1].toString()).to.eq(signature.R8[1].toString()); expect(decrypted.signature.S.toString()).to.eq(signature.S.toString()); @@ -40,7 +40,7 @@ describe("Commands & Messages", () => { it("decrypted message should have a valid signature", () => { const isValid = decrypted.command.verifySignature(decrypted.signature, pubKey); - expect(isValid).to.be.true; + expect(isValid).to.eq(true); }); it("Command.copy() should produce a deep copy", () => { @@ -73,7 +73,7 @@ describe("Commands & Messages", () => { ]); const m2 = m1.copy(); - expect(m2.equals(m1)).to.be.true; + expect(m2.equals(m1)).to.eq(true); }); it("message.asCircuitInputs() should return a array", () => { @@ -114,7 +114,7 @@ describe("Commands & Messages", () => { it("copy should produce a deep copy", () => { const c = tCommand.copy(); - expect(c.equals(tCommand)).to.be.true; + expect(c.equals(tCommand)).to.eq(true); }); }); }); diff --git a/domainobjs/ts/__tests__/keypair.test.ts b/domainobjs/ts/__tests__/keypair.test.ts index 39fe04dbdf..458d7617f5 100644 --- a/domainobjs/ts/__tests__/keypair.test.ts +++ b/domainobjs/ts/__tests__/keypair.test.ts @@ -1,13 +1,14 @@ -import { Keypair, PrivKey } from "../"; -import { genKeypair } from "maci-crypto"; import { expect } from "chai"; +import { genKeypair } from "maci-crypto"; + +import { Keypair, PrivKey } from ".."; describe("keypair", () => { it("the Keypair constructor should generate a random keypair if not provided a private key", () => { const k1 = new Keypair(); const k2 = new Keypair(); - expect(k1.equals(k2)).to.be.false; + expect(k1.equals(k2)).to.eq(false); expect(k1.privKey.rawPrivKey).not.to.eq(k2.privKey.rawPrivKey); }); @@ -22,26 +23,26 @@ describe("keypair", () => { it("should return false for two completely different keypairs", () => { const k1 = new Keypair(); const k2 = new Keypair(); - expect(k1.equals(k2)).to.be.false; + expect(k1.equals(k2)).to.eq(false); }); it("should return false for two keypairs with different private keys", () => { const k1 = new Keypair(); const k2 = new Keypair(); k2.privKey.rawPrivKey = BigInt(0); - expect(k1.equals(k2)).to.be.false; + expect(k1.equals(k2)).to.eq(false); }); it("should return false for two keypairs with different public keys", () => { const k1 = new Keypair(); const k2 = new Keypair(); k2.pubKey.rawPubKey[0] = BigInt(0); - expect(k1.equals(k2)).to.be.false; + expect(k1.equals(k2)).to.eq(false); }); it("should return true for two identical keypairs", () => { const k1 = new Keypair(); const k2 = k1.copy(); - expect(k1.equals(k2)).to.be.true; + expect(k1.equals(k2)).to.eq(true); }); it("copy should produce a deep copy", () => { diff --git a/domainobjs/ts/__tests__/privateKey.test.ts b/domainobjs/ts/__tests__/privateKey.test.ts index c1c8e0f79b..5b951a39d3 100644 --- a/domainobjs/ts/__tests__/privateKey.test.ts +++ b/domainobjs/ts/__tests__/privateKey.test.ts @@ -1,14 +1,15 @@ -import { Keypair, PrivKey } from "../"; import { expect } from "chai"; +import { Keypair, PrivKey } from ".."; + describe("privateKey", () => { it("PrivKey.serialize() and deserialize() should work correctly", () => { const k = new Keypair(); const sk1 = k.privKey; const s = sk1.serialize(); - expect(s.startsWith("macisk.")).to.be.true; - const d = "0x" + s.slice(7); + expect(s.startsWith("macisk.")).to.eq(true); + const d = `0x${s.slice(7)}`; expect(sk1.rawPrivKey.toString()).to.eq(BigInt(d).toString()); const c = PrivKey.deserialize(s); @@ -19,8 +20,8 @@ describe("privateKey", () => { const k = new Keypair(); const s = k.privKey.serialize(); - expect(PrivKey.isValidSerializedPrivKey(s)).to.be.true; - expect(PrivKey.isValidSerializedPrivKey(s.slice(1))).to.be.false; + expect(PrivKey.isValidSerializedPrivKey(s)).to.eq(true); + expect(PrivKey.isValidSerializedPrivKey(s.slice(1))).to.eq(false); }); it("PrivKey.copy() should produce a deep copy", () => { diff --git a/domainobjs/ts/__tests__/publicKey.test.ts b/domainobjs/ts/__tests__/publicKey.test.ts index 6cc751c345..c036fce2d6 100644 --- a/domainobjs/ts/__tests__/publicKey.test.ts +++ b/domainobjs/ts/__tests__/publicKey.test.ts @@ -1,15 +1,16 @@ -import { Keypair, PubKey } from "../"; -import { unpackPubKey } from "maci-crypto"; import { expect } from "chai"; +import { unpackPubKey } from "maci-crypto"; + +import { Keypair, PubKey } from ".."; describe("public key", () => { it("isValidSerializedPubKey() should work correctly", () => { const k = new Keypair(); const s = k.pubKey.serialize(); - expect(PubKey.isValidSerializedPubKey(s)).to.be.true; - expect(PubKey.isValidSerializedPubKey(s + "ffffffffffffffffffffffffffffff")).to.be.false; - expect(PubKey.isValidSerializedPubKey(s.slice(1))).to.be.false; + expect(PubKey.isValidSerializedPubKey(s)).to.eq(true); + expect(PubKey.isValidSerializedPubKey(`${s}ffffffffffffffffffffffffffffff`)).to.eq(false); + expect(PubKey.isValidSerializedPubKey(s.slice(1))).to.eq(false); }); it("serialize() and deserialize() should work correctly", () => { @@ -17,7 +18,7 @@ describe("public key", () => { const pk1 = k.pubKey; const s = pk1.serialize(); - expect(s.startsWith("macipk.")).to.be.true; + expect(s.startsWith("macipk.")).to.eq(true); const d = s.slice(7); const unpacked = unpackPubKey(Buffer.from(d, "hex")); diff --git a/domainobjs/ts/__tests__/stateLeaf.test.ts b/domainobjs/ts/__tests__/stateLeaf.test.ts index 64ce5805e0..6482695103 100644 --- a/domainobjs/ts/__tests__/stateLeaf.test.ts +++ b/domainobjs/ts/__tests__/stateLeaf.test.ts @@ -1,6 +1,7 @@ -import { Keypair, StateLeaf } from "../"; import { expect } from "chai"; +import { Keypair, StateLeaf } from ".."; + describe("State leaves", () => { const { pubKey } = new Keypair(); @@ -18,13 +19,13 @@ describe("State leaves", () => { const copy = stateLeaf.copy(); - expect(stateLeaf.equals(copy)).to.be.true; + expect(stateLeaf.equals(copy)).to.eq(true); }); it("genRandomLeaf should return a random leaf", () => { const randomLeaf = StateLeaf.genRandomLeaf(); const randomLeaf2 = StateLeaf.genRandomLeaf(); - expect(randomLeaf.equals(randomLeaf2)).to.be.false; + expect(randomLeaf.equals(randomLeaf2)).to.eq(false); }); it("asCircuitInputs should return an array", () => { @@ -41,7 +42,7 @@ describe("State leaves", () => { const stateLeaf2 = new StateLeaf(pubKey, BigInt(123), BigInt(1231267)); - expect(stateLeaf.equals(stateLeaf2)).to.be.true; + expect(stateLeaf.equals(stateLeaf2)).to.eq(true); }); it("equals should return false when comparing two different state leaves", () => { @@ -49,6 +50,6 @@ describe("State leaves", () => { const stateLeaf2 = new StateLeaf(pubKey, BigInt(123), BigInt(1231268)); - expect(stateLeaf.equals(stateLeaf2)).to.be.false; + expect(stateLeaf.equals(stateLeaf2)).to.eq(false); }); }); diff --git a/domainobjs/ts/__tests__/verifyingKey.test.ts b/domainobjs/ts/__tests__/verifyingKey.test.ts index c45c0f3575..237f724e98 100644 --- a/domainobjs/ts/__tests__/verifyingKey.test.ts +++ b/domainobjs/ts/__tests__/verifyingKey.test.ts @@ -1,13 +1,15 @@ -import * as path from "path"; -import * as fs from "fs"; -import { VerifyingKey } from "../"; import { expect } from "chai"; +import fs from "fs"; +import path from "path"; + +import { IVkObjectParams, VerifyingKey } from ".."; + describe("verifyingKey", () => { - it("Should convert a JSON file from snarkjs to a VerifyingKey", async () => { + it("Should convert a JSON file from snarkjs to a VerifyingKey", () => { const file = path.join(__dirname, "./artifacts/test_vk.json"); const j = fs.readFileSync(file).toString(); - const d = JSON.parse(j); + const d = JSON.parse(j) as IVkObjectParams; const vk = VerifyingKey.fromJSON(j); expect(d.vk_alpha_1[0]).to.eq(vk.alpha1.x.toString()); @@ -29,18 +31,18 @@ describe("verifyingKey", () => { expect(d.vk_delta_2[1][1]).to.eq(vk.delta2.y[0].toString()); expect(d.IC.length).to.eq(vk.ic.length); - for (let i = 0; i < d.IC.length; i++) { + for (let i = 0; i < d.IC.length; i += 1) { expect(d.IC[i][0]).to.eq(vk.ic[i].x.toString()); expect(d.IC[i][1]).to.eq(vk.ic[i].y.toString()); } }); - it("Copy should generate a deep copy", async () => { - const file = path.join(__dirname, "artifacts/test_vk.json"); + it("Copy should generate a deep copy", () => { + const file = path.resolve(__dirname, "artifacts/test_vk.json"); const j = fs.readFileSync(file).toString(); const vk = VerifyingKey.fromJSON(j); const vk2 = vk.copy(); - expect(vk.equals(vk2)).to.be.true; + expect(vk.equals(vk2)).to.eq(true); }); }); diff --git a/domainobjs/ts/ballot.ts b/domainobjs/ts/ballot.ts index d8eb4ee3d4..03eee2ddf5 100644 --- a/domainobjs/ts/ballot.ts +++ b/domainobjs/ts/ballot.ts @@ -1,14 +1,19 @@ -import assert = require("assert"); import { genRandomSalt, hash5, hashLeftRight, IncrementalQuinTree } from "maci-crypto"; +import assert from "assert"; + +import type { IJsonBallot } from "./types"; + /** * A Ballot represents a User's votes in a Poll, as well as their next valid * nonce. */ export class Ballot { - public votes: bigint[] = []; - public nonce = BigInt(0); - public voteOptionTreeDepth: number; + votes: bigint[] = []; + + nonce = BigInt(0); + + voteOptionTreeDepth: number; /** * Create a new Ballot instance @@ -19,7 +24,7 @@ export class Ballot { this.voteOptionTreeDepth = _voteOptionTreeDepth; assert(5 ** _voteOptionTreeDepth >= _numVoteOptions); assert(_numVoteOptions >= 0); - for (let i = 0; i < _numVoteOptions; i++) { + for (let i = 0; i < _numVoteOptions; i += 1) { this.votes.push(BigInt(0)); } } @@ -28,7 +33,7 @@ export class Ballot { * Generate an hash of this ballot * @returns The hash of the ballot */ - public hash = (): bigint => { + hash = (): bigint => { const vals = this.asArray(); return hashLeftRight(vals[0], vals[1]); }; @@ -37,21 +42,19 @@ export class Ballot { * Convert in a format suitable for the circuit * @returns the ballot as a BigInt array */ - public asCircuitInputs = (): bigint[] => { - return this.asArray(); - }; + asCircuitInputs = (): bigint[] => this.asArray(); /** * Convert in a an array of bigints * @notice this is the nonce and the root of the vote option tree * @returns the ballot as a bigint array */ - public asArray = (): bigint[] => { + asArray = (): bigint[] => { const lastIndex = this.votes.length - 1; - const foundIndex = this.votes.findIndex((vote, index) => this.votes[lastIndex - index] !== BigInt(0)); + const foundIndex = this.votes.findIndex((_, index) => this.votes[lastIndex - index] !== BigInt(0)); const lastIndexToInsert = foundIndex < 0 ? -1 : lastIndex - foundIndex; const voTree = new IncrementalQuinTree(this.voteOptionTreeDepth, BigInt(0), 5, hash5); - for (let i = 0; i <= lastIndexToInsert; i++) { + for (let i = 0; i <= lastIndexToInsert; i += 1) { voTree.insert(this.votes[i]); } @@ -62,7 +65,7 @@ export class Ballot { * Create a deep clone of this Ballot * @returns a copy of the ballot */ - public copy = (): Ballot => { + copy = (): Ballot => { const b = new Ballot(this.votes.length, this.voteOptionTreeDepth); b.votes = this.votes.map((x) => BigInt(x.toString())); @@ -75,7 +78,7 @@ export class Ballot { * @param b - The ballot to compare with * @returns whether the two ballots are equal */ - public equals(b: Ballot): boolean { + equals(b: Ballot): boolean { const isEqualVotes = this.votes.every((vote, index) => vote === b.votes[index]); return isEqualVotes ? b.nonce === this.nonce && this.votes.length === b.votes.length : false; } @@ -86,9 +89,9 @@ export class Ballot { * @param voteOptionTreeDepth How deep is the merkle tree holding the vote options * @returns a random Ballot */ - public static genRandomBallot(numVoteOptions: number, voteOptionTreeDepth: number) { + static genRandomBallot(numVoteOptions: number, voteOptionTreeDepth: number): Ballot { const ballot = new Ballot(numVoteOptions, voteOptionTreeDepth); - ballot.nonce = genRandomSalt() as bigint; + ballot.nonce = genRandomSalt(); return ballot; } @@ -98,7 +101,7 @@ export class Ballot { * @param voteOptionTreeDepth How deep is the merkle tree holding the vote options * @returns a Blank Ballot object */ - public static genBlankBallot(numVoteOptions: number, voteOptionTreeDepth: number) { + static genBlankBallot(numVoteOptions: number, voteOptionTreeDepth: number): Ballot { const ballot = new Ballot(numVoteOptions, voteOptionTreeDepth); return ballot; } @@ -106,7 +109,7 @@ export class Ballot { /** * Serialize to a JSON object */ - public toJSON() { + toJSON(): IJsonBallot { return { votes: this.votes.map((x) => x.toString()), nonce: this.nonce.toString(), @@ -119,9 +122,9 @@ export class Ballot { * @param json - the json representation * @returns the deserialized object as a Ballot instance */ - static fromJSON(json: any): Ballot { - const ballot = new Ballot(json.votes.length, parseInt(json.voteOptionTreeDepth)); - ballot.votes = json.votes.map((x: any) => BigInt(x)); + static fromJSON(json: IJsonBallot): Ballot { + const ballot = new Ballot(json.votes.length, Number.parseInt(json.voteOptionTreeDepth.toString(), 10)); + ballot.votes = json.votes.map((x) => BigInt(x)); ballot.nonce = BigInt(json.nonce); return ballot; } diff --git a/domainobjs/ts/commands.ts b/domainobjs/ts/commands/PCommand.ts similarity index 53% rename from domainobjs/ts/commands.ts rename to domainobjs/ts/commands/PCommand.ts index f1913aa1ac..2046a0102d 100644 --- a/domainobjs/ts/commands.ts +++ b/domainobjs/ts/commands/PCommand.ts @@ -9,123 +9,40 @@ import { Ciphertext, EcdhSharedKey, } from "maci-crypto"; + import assert from "assert"; -import { PubKey } from "./publicKey"; -import { PrivKey } from "./privateKey"; -import { Message } from "./message"; -/** - * @notice Base class for Commands - */ -export abstract class Command { - public cmdType: bigint; +import type { ICommand, IJsonPCommand } from "./types"; - constructor(cmdType: bigint) { - this.cmdType = cmdType; - } +import { Message } from "../message"; +import { PrivKey } from "../privateKey"; +import { PubKey } from "../publicKey"; - public copy(): Command { - return this; - } - abstract equals(command: Command): boolean; - - /** - * Serialize into a JSON object - */ - public toJSON() { - return { - cmdType: this.cmdType.toString(), - }; - } - - /** - * Deserialize into a Command instance - * @param json - */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - static fromJSON(json: any) { - throw new Error("Not implemented"); - } +export interface IDecryptMessage { + command: PCommand; + signature: Signature; } /** - * @notice Command for submitting a topup request + * @notice Unencrypted data whose fields include the user's public key, vote etc. + * This represents a Vote command. */ -export class TCommand extends Command { - public stateIndex: bigint; - public amount: bigint; - public pollId: bigint; +export class PCommand implements ICommand { + cmdType: bigint; - /** - * Create a new TCommand - * @param stateIndex the state index of the user - * @param amount the amount of voice credits - * @param pollId the poll ID - */ - constructor(stateIndex: bigint, amount: bigint, pollId: bigint) { - super(BigInt(2)); - this.stateIndex = stateIndex; - this.amount = amount; - this.pollId = pollId; - } + stateIndex: bigint; - /** - * Create a deep clone of this TCommand - * @returns a copy of the TCommand - */ - public copy = (): TCommand => { - return new TCommand(this.stateIndex, this.amount, this.pollId); - }; + newPubKey: PubKey; - /** - * Check whether this command has deep equivalence to another command - * @param command the command to compare with - * @returns whether they are equal or not - */ - public equals = (command: T): boolean => { - return ( - this.stateIndex === command.stateIndex && - this.amount === command.amount && - this.pollId === command.pollId && - this.cmdType === command.cmdType - ); - }; + voteOptionIndex: bigint; - /** - * Serialize into a JSON object - */ - public toJSON() { - return { - stateIndex: this.stateIndex.toString(), - amount: this.amount.toString(), - cmdType: this.cmdType.toString(), - pollId: this.pollId.toString(), - }; - } + newVoteWeight: bigint; - /** - * Deserialize into a TCommand object - * @param json - the json representation - * @returns the TCommand instance - */ - static fromJSON(json: any): TCommand { - const command = new TCommand(BigInt(json.stateIndex), BigInt(json.amount), json.pollId); - return command; - } -} + nonce: bigint; -/** - * @notice Unencrypted data whose fields include the user's public key, vote etc. - * This represents a Vote command. - */ -export class PCommand extends Command { - public stateIndex: bigint; - public newPubKey: PubKey; - public voteOptionIndex: bigint; - public newVoteWeight: bigint; - public nonce: bigint; - public pollId: bigint; - public salt: bigint; + pollId: bigint; + + salt: bigint; /** * Create a new PCommand @@ -146,7 +63,8 @@ export class PCommand extends Command { pollId: bigint, salt: bigint = genRandomSalt(), ) { - super(BigInt(1)); + this.cmdType = BigInt(1); + const limit50Bits = BigInt(2 ** 50); assert(limit50Bits >= stateIndex); assert(limit50Bits >= voteOptionIndex); @@ -167,8 +85,8 @@ export class PCommand extends Command { * Create a deep clone of this PCommand * @returns a copy of the PCommand */ - public copy = (): PCommand => { - return new PCommand( + copy = (): T => + new PCommand( BigInt(this.stateIndex.toString()), this.newPubKey.copy(), BigInt(this.voteOptionIndex.toString()), @@ -176,8 +94,7 @@ export class PCommand extends Command { BigInt(this.nonce.toString()), BigInt(this.pollId.toString()), BigInt(this.salt.toString()), - ); - }; + ) as unknown as T; /** * @notice Returns this Command as an array. Note that 5 of the Command's fields @@ -185,57 +102,50 @@ export class PCommand extends Command { * smaller and thereby save gas when the user publishes a message. * @returns bigint[] - the command as an array */ - public asArray = (): bigint[] => { - const p = - BigInt(`${this.stateIndex}`) + - (BigInt(`${this.voteOptionIndex}`) << BigInt(50)) + - (BigInt(`${this.newVoteWeight}`) << BigInt(100)) + - (BigInt(`${this.nonce}`) << BigInt(150)) + - (BigInt(`${this.pollId}`) << BigInt(200)); - - const a = [p, ...this.newPubKey.asArray(), this.salt]; - assert(a.length === 4); - return a; - }; + asArray = (): bigint[] => { + /* eslint-disable no-bitwise */ + const params = + BigInt(this.stateIndex) + + (BigInt(this.voteOptionIndex) << BigInt(50)) + + (BigInt(this.newVoteWeight) << BigInt(100)) + + (BigInt(this.nonce) << BigInt(150)) + + (BigInt(this.pollId) << BigInt(200)); + /* eslint-enable no-bitwise */ - public asCircuitInputs = (): bigint[] => { - return this.asArray(); + const command = [params, ...this.newPubKey.asArray(), this.salt]; + assert(command.length === 4); + + return command; }; + asCircuitInputs = (): bigint[] => this.asArray(); + /* * Check whether this command has deep equivalence to another command */ - public equals = (command: PCommand): boolean => { - return ( - this.stateIndex === command.stateIndex && - this.newPubKey.equals(command.newPubKey) && - this.voteOptionIndex === command.voteOptionIndex && - this.newVoteWeight === command.newVoteWeight && - this.nonce === command.nonce && - this.pollId === command.pollId && - this.salt === command.salt - ); - }; + equals = (command: PCommand): boolean => + this.stateIndex === command.stateIndex && + this.newPubKey.equals(command.newPubKey) && + this.voteOptionIndex === command.voteOptionIndex && + this.newVoteWeight === command.newVoteWeight && + this.nonce === command.nonce && + this.pollId === command.pollId && + this.salt === command.salt; - public hash = (): bigint => { - return hash4(this.asArray()) as bigint; - }; + hash = (): bigint => hash4(this.asArray()); /** * @notice Signs this command and returns a Signature. */ - public sign = (privKey: PrivKey): Signature => { - return sign(privKey.rawPrivKey, this.hash()); - }; + sign = (privKey: PrivKey): Signature => sign(privKey.rawPrivKey, this.hash()); /** * @notice Returns true if the given signature is a correct signature of this * command and signed by the private key associated with the given public * key. */ - public verifySignature = (signature: Signature, pubKey: PubKey): boolean => { - return verifySignature(this.hash(), signature, pubKey.rawPubKey); - }; + verifySignature = (signature: Signature, pubKey: PubKey): boolean => + verifySignature(this.hash(), signature, pubKey.rawPubKey); /** * @notice Encrypts this command along with a signature to produce a Message. @@ -247,7 +157,7 @@ export class PCommand extends Command { * 5. nonce * 6. poll ID */ - public encrypt = (signature: Signature, sharedKey: EcdhSharedKey): Message => { + encrypt = (signature: Signature, sharedKey: EcdhSharedKey): Message => { const plaintext = [...this.asArray(), signature.R8[0], signature.R8[1], signature.S]; assert(plaintext.length === 7); @@ -264,7 +174,7 @@ export class PCommand extends Command { * @param {Message} message - the message to decrypt * @param {EcdhSharedKey} sharedKey - the shared key to use for decryption */ - public static decrypt = (message: Message, sharedKey: EcdhSharedKey) => { + static decrypt = (message: Message, sharedKey: EcdhSharedKey): IDecryptMessage => { const decrypted = decrypt(message.data, sharedKey, BigInt(0), 7); const p = BigInt(decrypted[0].toString()); @@ -274,9 +184,9 @@ export class PCommand extends Command { // shift left by pos // AND with val // shift right by pos - const extract = (val: bigint, pos: number): bigint => { - return BigInt((((BigInt(1) << BigInt(50)) - BigInt(1)) << BigInt(pos)) & val) >> BigInt(pos); - }; + const extract = (val: bigint, pos: number): bigint => + // eslint-disable-next-line no-bitwise + BigInt((((BigInt(1) << BigInt(50)) - BigInt(1)) << BigInt(pos)) & val) >> BigInt(pos); // p is a packed value // bits 0 - 50: stateIndex @@ -293,7 +203,7 @@ export class PCommand extends Command { const newPubKey = new PubKey([decrypted[1], decrypted[2]]); const salt = decrypted[3]; - const command = new PCommand(stateIndex, newPubKey, voteOptionIndex, newVoteWeight, nonce, pollId, salt as bigint); + const command = new PCommand(stateIndex, newPubKey, voteOptionIndex, newVoteWeight, nonce, pollId, salt); const signature = { R8: [decrypted[4], decrypted[5]], @@ -306,7 +216,7 @@ export class PCommand extends Command { /** * Serialize into a JSON object */ - public toJSON() { + toJSON(): IJsonPCommand { return { stateIndex: this.stateIndex.toString(), newPubKey: this.newPubKey.serialize(), @@ -324,7 +234,7 @@ export class PCommand extends Command { * @param json * @returns a PComamnd instance */ - static fromJSON(json: any): PCommand { + static fromJSON(json: IJsonPCommand): PCommand { const command = new PCommand( BigInt(json.stateIndex), PubKey.deserialize(json.newPubKey), @@ -334,6 +244,7 @@ export class PCommand extends Command { BigInt(json.pollId), BigInt(json.salt), ); + return command; } } diff --git a/domainobjs/ts/commands/TCommand.ts b/domainobjs/ts/commands/TCommand.ts new file mode 100644 index 0000000000..86f340556b --- /dev/null +++ b/domainobjs/ts/commands/TCommand.ts @@ -0,0 +1,65 @@ +import type { ICommand, IJsonTCommand } from "./types"; + +/** + * @notice Command for submitting a topup request + */ +export class TCommand implements ICommand { + cmdType: bigint; + + stateIndex: bigint; + + amount: bigint; + + pollId: bigint; + + /** + * Create a new TCommand + * @param stateIndex the state index of the user + * @param amount the amount of voice credits + * @param pollId the poll ID + */ + constructor(stateIndex: bigint, amount: bigint, pollId: bigint) { + this.cmdType = BigInt(2); + this.stateIndex = stateIndex; + this.amount = amount; + this.pollId = pollId; + } + + /** + * Create a deep clone of this TCommand + * @returns a copy of the TCommand + */ + copy = (): T => new TCommand(this.stateIndex, this.amount, this.pollId) as T; + + /** + * Check whether this command has deep equivalence to another command + * @param command the command to compare with + * @returns whether they are equal or not + */ + equals = (command: TCommand): boolean => + this.stateIndex === command.stateIndex && + this.amount === command.amount && + this.pollId === command.pollId && + this.cmdType === command.cmdType; + + /** + * Serialize into a JSON object + */ + toJSON(): IJsonTCommand { + return { + stateIndex: this.stateIndex.toString(), + amount: this.amount.toString(), + cmdType: this.cmdType.toString(), + pollId: this.pollId.toString(), + }; + } + + /** + * Deserialize into a TCommand object + * @param json - the json representation + * @returns the TCommand instance + */ + static fromJSON(json: IJsonTCommand): TCommand { + return new TCommand(BigInt(json.stateIndex), BigInt(json.amount), BigInt(json.pollId)); + } +} diff --git a/domainobjs/ts/commands/index.ts b/domainobjs/ts/commands/index.ts new file mode 100644 index 0000000000..24323c43af --- /dev/null +++ b/domainobjs/ts/commands/index.ts @@ -0,0 +1,3 @@ +export { TCommand } from "./TCommand"; +export { PCommand } from "./PCommand"; +export type { ICommand, IJsonCommand, IJsonTCommand, IJsonPCommand } from "./types"; diff --git a/domainobjs/ts/commands/types.ts b/domainobjs/ts/commands/types.ts new file mode 100644 index 0000000000..03a96de92b --- /dev/null +++ b/domainobjs/ts/commands/types.ts @@ -0,0 +1,38 @@ +/** + * @notice A parent interface for all the commands + */ +export interface ICommand { + cmdType: bigint; + copy: () => T; + equals: (command: T) => boolean; + toJSON: () => unknown; +} + +/** + * @notice An interface representing a generic json command + */ +export interface IJsonCommand { + cmdType: string; +} + +/** + * @notice An interface representing a json T command + */ +export interface IJsonTCommand extends IJsonCommand { + stateIndex: string; + amount: string; + pollId: string; +} + +/** + * @notice An interface representing a json P command + */ +export interface IJsonPCommand extends IJsonCommand { + stateIndex: string; + newPubKey: string; + voteOptionIndex: string; + newVoteWeight: string; + nonce: string; + pollId: string; + salt: string; +} diff --git a/domainobjs/ts/constants.ts b/domainobjs/ts/constants.ts index e13e81c5ae..aae4709cb1 100644 --- a/domainobjs/ts/constants.ts +++ b/domainobjs/ts/constants.ts @@ -1,6 +1,4 @@ import { StateLeaf } from "./stateLeaf"; -export const SERIALIZED_PRIV_KEY_PREFIX = "macisk."; -export const SERIALIZED_PUB_KEY_PREFIX = "macipk."; export const blankStateLeaf = StateLeaf.genBlankLeaf(); export const blankStateLeafHash = blankStateLeaf.hash(); diff --git a/domainobjs/ts/index.ts b/domainobjs/ts/index.ts index 66acbf0efa..8d4a5ffb7f 100644 --- a/domainobjs/ts/index.ts +++ b/domainobjs/ts/index.ts @@ -2,18 +2,40 @@ export { Ballot } from "./ballot"; export { Message } from "./message"; -export { PrivKey } from "./privateKey"; +export { PrivKey, SERIALIZED_PRIV_KEY_PREFIX } from "./privateKey"; -export { PubKey } from "./publicKey"; +export { PubKey, SERIALIZED_PUB_KEY_PREFIX } from "./publicKey"; export { Keypair } from "./keyPair"; export { StateLeaf } from "./stateLeaf"; -export { SERIALIZED_PRIV_KEY_PREFIX, SERIALIZED_PUB_KEY_PREFIX, blankStateLeaf, blankStateLeafHash } from "./constants"; - -export { Proof, IStateLeaf, VoteOptionTreeLeaf } from "./types"; - -export { Command, TCommand, PCommand } from "./commands"; +export { blankStateLeaf, blankStateLeafHash } from "./constants"; + +export type { + Proof, + IStateLeaf, + VoteOptionTreeLeaf, + IJsonKeyPair, + IJsonPrivateKey, + IJsonPublicKey, + IJsonStateLeaf, + IG1ContractParams, + IG2ContractParams, + IVkContractParams, + IVkObjectParams, + IStateLeafContractParams, + IMessageContractParams, + IJsonBallot, +} from "./types"; + +export { + type ICommand, + type IJsonCommand, + type IJsonTCommand, + type IJsonPCommand, + TCommand, + PCommand, +} from "./commands"; export { VerifyingKey } from "./verifyingKey"; diff --git a/domainobjs/ts/keyPair.ts b/domainobjs/ts/keyPair.ts index 7cf85c70c2..a73f8427c1 100644 --- a/domainobjs/ts/keyPair.ts +++ b/domainobjs/ts/keyPair.ts @@ -1,5 +1,9 @@ +import { EcdhSharedKey, genEcdhSharedKey, genKeypair, genPubKey } from "maci-crypto"; + import assert from "assert"; -import { genEcdhSharedKey, genKeypair, genPubKey } from "maci-crypto"; + +import type { IJsonKeyPair } from "./types"; + import { PrivKey } from "./privateKey"; import { PubKey } from "./publicKey"; @@ -10,8 +14,9 @@ import { PubKey } from "./publicKey"; * A MACI keypair is comprised of a MACI public key and a MACI private key */ export class Keypair { - public privKey: PrivKey; - public pubKey: PubKey; + privKey: PrivKey; + + pubKey: PubKey; /** * Create a new instance of a Keypair @@ -33,9 +38,7 @@ export class Keypair { * Create a deep clone of this Keypair * @returns a copy of the Keypair */ - public copy = (): Keypair => { - return new Keypair(this.privKey.copy()); - }; + copy = (): Keypair => new Keypair(this.privKey.copy()); /** * Generate a shared key @@ -43,7 +46,7 @@ export class Keypair { * @param pubKey * @returns */ - public static genEcdhSharedKey(privKey: PrivKey, pubKey: PubKey) { + static genEcdhSharedKey(privKey: PrivKey, pubKey: PubKey): EcdhSharedKey { return genEcdhSharedKey(privKey.rawPrivKey, pubKey.rawPubKey); } @@ -52,7 +55,7 @@ export class Keypair { * @param keypair the keypair to compare with * @returns whether they are equal or not */ - public equals(keypair: Keypair): boolean { + equals(keypair: Keypair): boolean { const equalPrivKey = this.privKey.rawPrivKey === keypair.privKey.rawPrivKey; const equalPubKey = this.pubKey.rawPubKey[0] === keypair.pubKey.rawPubKey[0] && @@ -60,7 +63,7 @@ export class Keypair { // If this assertion fails, something is very wrong and this function // should not return anything - // XOR is equivalent to: (x && !y) || (!x && y ) + // eslint-disable-next-line no-bitwise assert(!(+equalPrivKey ^ +equalPubKey)); return equalPrivKey; @@ -69,7 +72,7 @@ export class Keypair { /** * Serialize into a JSON object */ - public toJSON() { + toJSON(): IJsonKeyPair { return { privKey: this.privKey.serialize(), pubKey: this.pubKey.serialize(), @@ -81,7 +84,7 @@ export class Keypair { * @param json * @returns a keypair instance */ - static fromJSON(json: any): Keypair { + static fromJSON(json: IJsonKeyPair): Keypair { return new Keypair(PrivKey.deserialize(json.privKey)); } } diff --git a/domainobjs/ts/message.ts b/domainobjs/ts/message.ts index 5cff9cd0cb..18c3485d35 100644 --- a/domainobjs/ts/message.ts +++ b/domainobjs/ts/message.ts @@ -1,14 +1,20 @@ -import assert = require("assert"); -import { PubKey } from "./publicKey"; import { hash13 } from "maci-crypto"; +import assert from "assert"; + +import type { IMessageContractParams } from "./types"; + +import { PubKey } from "./publicKey"; + /** * @notice An encrypted command and signature. */ export class Message { - public msgType: bigint; - public data: bigint[]; - public static DATA_LENGTH = 10; + msgType: bigint; + + data: bigint[]; + + static DATA_LENGTH = 10; /** * Create a new instance of a Message @@ -25,55 +31,46 @@ export class Message { * Return the message as an array of bigints * @returns the message as an array of bigints */ - private asArray = (): bigint[] => { - return [this.msgType].concat(this.data); - }; + private asArray = (): bigint[] => [this.msgType].concat(this.data); /** * Return the message as a contract param * @returns the message as a contract param */ - public asContractParam = () => { - return { - msgType: this.msgType.toString(), - data: this.data.map((x: bigint) => x.toString()), - }; - }; + asContractParam = (): IMessageContractParams => ({ + msgType: this.msgType.toString(), + data: this.data.map((x: bigint) => x.toString()), + }); /** * Return the message as a circuit input * @returns the message as a circuit input */ - public asCircuitInputs = (): bigint[] => { - return this.asArray(); - }; + asCircuitInputs = (): bigint[] => this.asArray(); /** * Hash the message data and a public key * @param encPubKey the public key that is used to encrypt this message * @returns the hash of the message data and the public key */ - public hash = (encPubKey: PubKey): bigint => { - return hash13([...[this.msgType], ...this.data, ...encPubKey.rawPubKey]) as bigint; - }; + hash = (encPubKey: PubKey): bigint => hash13([...[this.msgType], ...this.data, ...encPubKey.rawPubKey]); /** * Create a copy of the message * @returns a copy of the message */ - public copy = (): Message => { - return new Message( + copy = (): Message => + new Message( BigInt(this.msgType.toString()), this.data.map((x: bigint) => BigInt(x.toString())), ); - }; /** * Check if two messages are equal * @param m the message to compare with * @returns the result of the comparison */ - public equals = (m: Message): boolean => { + equals = (m: Message): boolean => { if (this.data.length !== m.data.length) { return false; } @@ -87,7 +84,7 @@ export class Message { /** * Serialize to a JSON object */ - public toJSON() { + toJSON(): IMessageContractParams { return this.asContractParam(); } @@ -96,10 +93,10 @@ export class Message { * @param json - the json representation * @returns the deserialized object as a Message instance */ - static fromJSON(json: any): Message { + static fromJSON(json: IMessageContractParams): Message { return new Message( BigInt(json.msgType), - json.data.map((x: any) => BigInt(x)), + json.data.map((x) => BigInt(x)), ); } } diff --git a/domainobjs/ts/privateKey.ts b/domainobjs/ts/privateKey.ts index 13b8076bfb..d191b0b0b0 100644 --- a/domainobjs/ts/privateKey.ts +++ b/domainobjs/ts/privateKey.ts @@ -1,5 +1,8 @@ import { SNARK_FIELD_SIZE, formatPrivKeyForBabyJub, PrivKey as RawPrivKey } from "maci-crypto"; -import { SERIALIZED_PRIV_KEY_PREFIX } from "./constants"; + +import type { IJsonPrivateKey } from "./types"; + +export const SERIALIZED_PRIV_KEY_PREFIX = "macisk."; /** * @notice PrivKey is a TS Class representing a MACI PrivateKey (on the jubjub curve) @@ -9,7 +12,7 @@ import { SERIALIZED_PRIV_KEY_PREFIX } from "./constants"; * A raw MACI private key can be thought as a point on the baby jubjub curve */ export class PrivKey { - public rawPrivKey: RawPrivKey; + rawPrivKey: RawPrivKey; /** * Generate a new Private key object @@ -23,32 +26,26 @@ export class PrivKey { * Create a copy of this Private key * @returns a copy of the Private key */ - public copy = (): PrivKey => { - return new PrivKey(BigInt(this.rawPrivKey.toString())); - }; + copy = (): PrivKey => new PrivKey(BigInt(this.rawPrivKey.toString())); /** * Return this Private key as a circuit input * @returns the Private key as a circuit input */ - public asCircuitInputs = () => { - return formatPrivKeyForBabyJub(this.rawPrivKey).toString(); - }; + asCircuitInputs = (): string => formatPrivKeyForBabyJub(this.rawPrivKey).toString(); /** * Serialize the private key * @returns the serialized private key */ - public serialize = (): string => { - return SERIALIZED_PRIV_KEY_PREFIX + this.rawPrivKey.toString(16); - }; + serialize = (): string => SERIALIZED_PRIV_KEY_PREFIX + this.rawPrivKey.toString(16); /** * Deserialize the private key * @param s the serialized private key * @returns the deserialized private key */ - public static deserialize = (s: string): PrivKey => { + static deserialize = (s: string): PrivKey => { const x = s.slice(SERIALIZED_PRIV_KEY_PREFIX.length); return new PrivKey(BigInt(`0x${x}`)); }; @@ -58,7 +55,7 @@ export class PrivKey { * @param s the serialized private key * @returns whether it is a valid serialized private key */ - public static isValidSerializedPrivKey = (s: string): boolean => { + static isValidSerializedPrivKey = (s: string): boolean => { const correctPrefix = s.startsWith(SERIALIZED_PRIV_KEY_PREFIX); const x = s.slice(SERIALIZED_PRIV_KEY_PREFIX.length); @@ -73,7 +70,7 @@ export class PrivKey { /** * Serialize this object */ - public toJSON() { + toJSON(): IJsonPrivateKey { return { privKey: this.serialize(), }; @@ -84,7 +81,7 @@ export class PrivKey { * @param json - the json object * @returns the deserialized object as a PrivKey instance */ - static fromJSON(json: any): PrivKey { + static fromJSON(json: IJsonPrivateKey): PrivKey { return PrivKey.deserialize(json.privKey); } } diff --git a/domainobjs/ts/publicKey.ts b/domainobjs/ts/publicKey.ts index 4acc9c3502..9a46846943 100644 --- a/domainobjs/ts/publicKey.ts +++ b/domainobjs/ts/publicKey.ts @@ -1,7 +1,10 @@ -import assert from "assert"; import { SNARK_FIELD_SIZE, hashLeftRight, packPubKey, unpackPubKey, PubKey as RawPubKey } from "maci-crypto"; -import { SERIALIZED_PUB_KEY_PREFIX } from "./constants"; +import assert from "assert"; + +import type { IJsonPublicKey, IG1ContractParams } from "./types"; + +export const SERIALIZED_PUB_KEY_PREFIX = "macipk."; /** * @notice A class representing a public key * This is a MACI public key, which is not to be @@ -11,7 +14,7 @@ import { SERIALIZED_PUB_KEY_PREFIX } from "./constants"; * BigIntegers (x, y) representing a point on the baby jubjub curve */ export class PubKey { - public rawPubKey: RawPubKey; + rawPubKey: RawPubKey; /** * Create a new instance of a public key @@ -19,8 +22,8 @@ export class PubKey { */ constructor(rawPubKey: RawPubKey) { assert(rawPubKey.length === 2); - assert((rawPubKey[0] as bigint) < SNARK_FIELD_SIZE); - assert((rawPubKey[1] as bigint) < SNARK_FIELD_SIZE); + assert(rawPubKey[0] < SNARK_FIELD_SIZE); + assert(rawPubKey[1] < SNARK_FIELD_SIZE); this.rawPubKey = rawPubKey; } @@ -28,16 +31,15 @@ export class PubKey { * Create a copy of the public key * @returns a copy of the public key */ - public copy = (): PubKey => { - return new PubKey([BigInt(this.rawPubKey[0].toString()), BigInt(this.rawPubKey[1].toString())]); - }; + copy = (): PubKey => new PubKey([BigInt(this.rawPubKey[0].toString()), BigInt(this.rawPubKey[1].toString())]); /** * Return this public key as smart contract parameters * @returns the public key as smart contract parameters */ - public asContractParam = (): any => { + asContractParam = (): IG1ContractParams => { const [x, y] = this.rawPubKey; + return { x: x.toString(), y: y.toString(), @@ -48,27 +50,23 @@ export class PubKey { * Return this public key as circuit inputs * @returns an array of strings */ - public asCircuitInputs = (): string[] => { - return this.rawPubKey.map((x) => x.toString()); - }; + asCircuitInputs = (): string[] => this.rawPubKey.map((x) => x.toString()); /** * Return this public key as an array of bigints * @returns the public key as an array of bigints */ - public asArray = (): bigint[] => { - return [this.rawPubKey[0] as bigint, this.rawPubKey[1] as bigint]; - }; + asArray = (): bigint[] => [this.rawPubKey[0], this.rawPubKey[1]]; /** * Generate a serialized public key from this public key object * @returns the string representation of a serialized public key */ - public serialize = (): string => { + serialize = (): string => { const { x, y } = this.asContractParam(); // Blank leaves have pubkey [0, 0], which packPubKey does not support if (BigInt(x) === BigInt(0) && BigInt(y) === BigInt(0)) { - return SERIALIZED_PUB_KEY_PREFIX + "z"; + return `${SERIALIZED_PUB_KEY_PREFIX}z`; } const packed = packPubKey(this.rawPubKey).toString("hex"); return SERIALIZED_PUB_KEY_PREFIX + packed.toString(); @@ -78,27 +76,23 @@ export class PubKey { * Hash the two baby jubjub coordinates * @returns the hash of this public key */ - public hash = (): bigint => { - return hashLeftRight(this.rawPubKey[0], this.rawPubKey[1]); - }; + hash = (): bigint => hashLeftRight(this.rawPubKey[0], this.rawPubKey[1]); /** * Check whether this public key equals to another public key * @param p the public key to compare with * @returns whether they match */ - public equals = (p: PubKey): boolean => { - return this.rawPubKey[0] === p.rawPubKey[0] && this.rawPubKey[1] === p.rawPubKey[1]; - }; + equals = (p: PubKey): boolean => this.rawPubKey[0] === p.rawPubKey[0] && this.rawPubKey[1] === p.rawPubKey[1]; /** * Deserialize a serialized public key * @param s the serialized public key * @returns the deserialized public key */ - public static deserialize = (s: string): PubKey => { + static deserialize = (s: string): PubKey => { // Blank leaves have pubkey [0, 0], which packPubKey does not support - if (s === SERIALIZED_PUB_KEY_PREFIX + "z") { + if (s === `${SERIALIZED_PUB_KEY_PREFIX}z`) { return new PubKey([BigInt(0), BigInt(0)]); } @@ -112,7 +106,7 @@ export class PubKey { * @param s the serialized public key * @returns whether the serialized public key is valid */ - public static isValidSerializedPubKey = (s: string): boolean => { + static isValidSerializedPubKey = (s: string): boolean => { const correctPrefix = s.startsWith(SERIALIZED_PUB_KEY_PREFIX); try { @@ -126,7 +120,7 @@ export class PubKey { /** * Serialize this object */ - public toJSON() { + toJSON(): IJsonPublicKey { return { pubKey: this.serialize(), }; @@ -137,7 +131,7 @@ export class PubKey { * @param json - the json object * @returns PubKey */ - static fromJSON(json: any): PubKey { + static fromJSON(json: IJsonPublicKey): PubKey { return PubKey.deserialize(json.pubKey); } } diff --git a/domainobjs/ts/stateLeaf.ts b/domainobjs/ts/stateLeaf.ts index 985bd9f402..1ad817f0ee 100644 --- a/domainobjs/ts/stateLeaf.ts +++ b/domainobjs/ts/stateLeaf.ts @@ -1,5 +1,7 @@ import { genRandomSalt, hash4 } from "maci-crypto"; -import { IStateLeaf } from "./types"; + +import type { IJsonStateLeaf, IStateLeaf, IStateLeafContractParams } from "./types"; + import { Keypair } from "./keyPair"; import { PubKey } from "./publicKey"; @@ -8,9 +10,11 @@ import { PubKey } from "./publicKey"; * public keys to voice credit balances */ export class StateLeaf implements IStateLeaf { - public pubKey: PubKey; - public voiceCreditBalance: bigint; - public timestamp: bigint; + pubKey: PubKey; + + voiceCreditBalance: bigint; + + timestamp: bigint; /** * Create a new instance of a state leaf @@ -28,7 +32,7 @@ export class StateLeaf implements IStateLeaf { * Crate a deep copy of the object * @returns a copy of the state leaf */ - public copy(): StateLeaf { + copy(): StateLeaf { return new StateLeaf( this.pubKey.copy(), BigInt(this.voiceCreditBalance.toString()), @@ -40,7 +44,7 @@ export class StateLeaf implements IStateLeaf { * Generate a blank state leaf * @returns a blank state leaf */ - public static genBlankLeaf(): StateLeaf { + static genBlankLeaf(): StateLeaf { // The public key for a blank state leaf is the first Pedersen base // point from iden3's circomlib implementation of the Pedersen hash. // Since it is generated using a hash-to-curve function, we are @@ -63,40 +67,34 @@ export class StateLeaf implements IStateLeaf { * Generate a random leaf (random salt and random key pair) * @returns a random state leaf */ - public static genRandomLeaf() { + static genRandomLeaf(): StateLeaf { const keypair = new Keypair(); - return new StateLeaf(keypair.pubKey, genRandomSalt() as bigint, BigInt(0)); + return new StateLeaf(keypair.pubKey, genRandomSalt(), BigInt(0)); } /** * Return this state leaf as an array of bigints * @returns the state leaf as an array of bigints */ - private asArray = (): bigint[] => { - return [...this.pubKey.asArray(), this.voiceCreditBalance, this.timestamp]; - }; + private asArray = (): bigint[] => [...this.pubKey.asArray(), this.voiceCreditBalance, this.timestamp]; /** * Return this state leaf as an array of bigints * @returns the state leaf as an array of bigints */ - public asCircuitInputs = (): bigint[] => { - return this.asArray(); - }; + asCircuitInputs = (): bigint[] => this.asArray(); /** * Hash this state leaf (first convert as array) * @returns the has of the state leaf elements */ - public hash = (): bigint => { - return hash4(this.asArray()) as bigint; - }; + hash = (): bigint => hash4(this.asArray()); /** * Return this state leaf as a contract param * @returns the state leaf as a contract param (object) */ - public asContractParam() { + asContractParam(): IStateLeafContractParams { return { pubKey: this.pubKey.asContractParam(), voiceCreditBalance: this.voiceCreditBalance.toString(), @@ -109,7 +107,7 @@ export class StateLeaf implements IStateLeaf { * @param s the state leaf to compare with * @returns whether they are equal or not */ - public equals(s: StateLeaf): boolean { + equals(s: StateLeaf): boolean { return ( this.pubKey.equals(s.pubKey) && this.voiceCreditBalance === s.voiceCreditBalance && this.timestamp === s.timestamp ); @@ -121,7 +119,7 @@ export class StateLeaf implements IStateLeaf { * @notice convert the voice credit balance and timestamp to a hex string * @returns */ - public serialize = (): string => { + serialize = (): string => { const j = [this.pubKey.serialize(), this.voiceCreditBalance.toString(16), this.timestamp.toString(16)]; return Buffer.from(JSON.stringify(j, null, 0), "utf8").toString("base64url"); @@ -134,15 +132,15 @@ export class StateLeaf implements IStateLeaf { */ static deserialize = (serialized: string): StateLeaf => { const base64 = serialized.replace(/-/g, "+").replace(/_/g, "/"); - const j = JSON.parse(Buffer.from(base64, "base64").toString("utf8")); + const json = JSON.parse(Buffer.from(base64, "base64").toString("utf8")) as [string, string, string]; - return new StateLeaf(PubKey.deserialize(j[0]), BigInt(`0x${j[1]}`), BigInt(`0x${j[2]}`)); + return new StateLeaf(PubKey.deserialize(json[0]), BigInt(`0x${json[1]}`), BigInt(`0x${json[2]}`)); }; /** * Serialize to a JSON object */ - public toJSON() { + toJSON(): IJsonStateLeaf { return { pubKey: this.pubKey.serialize(), voiceCreditBalance: this.voiceCreditBalance.toString(), @@ -155,7 +153,7 @@ export class StateLeaf implements IStateLeaf { * @param json - the json representation * @returns the deserialized object as a StateLeaf instance */ - static fromJSON(json: any): StateLeaf { + static fromJSON(json: IJsonStateLeaf): StateLeaf { return new StateLeaf(PubKey.deserialize(json.pubKey), BigInt(json.voiceCreditBalance), BigInt(json.timestamp)); } } diff --git a/domainobjs/ts/types.ts b/domainobjs/ts/types.ts index a257b0ec41..4919ca07f8 100644 --- a/domainobjs/ts/types.ts +++ b/domainobjs/ts/types.ts @@ -1,5 +1,5 @@ -import { G1Point, G2Point } from "maci-crypto"; -import { PubKey } from "./publicKey"; +import type { PubKey } from "./publicKey"; +import type { G1Point, G2Point } from "maci-crypto"; /** * @notice An interface representing a zk-SNARK proof @@ -24,3 +24,63 @@ export interface IStateLeaf { export interface VoteOptionTreeLeaf { votes: bigint; } + +export interface IJsonKeyPair { + privKey: string; + pubKey: string; +} + +export type IJsonPrivateKey = Pick; + +export type IJsonPublicKey = Pick; + +export interface IJsonStateLeaf { + pubKey: string; + voiceCreditBalance: string; + timestamp: string; +} + +export type BigNumberish = number | string | bigint; + +export interface IG1ContractParams { + x: BigNumberish; + y: BigNumberish; +} + +export interface IG2ContractParams { + x: BigNumberish[]; + y: BigNumberish[]; +} + +export interface IVkContractParams { + alpha1: IG1ContractParams; + beta2: IG2ContractParams; + gamma2: IG2ContractParams; + delta2: IG2ContractParams; + ic: IG1ContractParams[]; +} + +export interface IVkObjectParams { + vk_alpha_1: [BigNumberish, BigNumberish]; + vk_beta_2: [[BigNumberish, BigNumberish], [BigNumberish, BigNumberish]]; + vk_gamma_2: [[BigNumberish, BigNumberish], [BigNumberish, BigNumberish]]; + vk_delta_2: [[BigNumberish, BigNumberish], [BigNumberish, BigNumberish]]; + IC: [BigNumberish, BigNumberish][]; +} + +export interface IStateLeafContractParams { + pubKey: IG1ContractParams; + voiceCreditBalance: BigNumberish; + timestamp: BigNumberish; +} + +export interface IMessageContractParams { + msgType: string; + data: BigNumberish[]; +} + +export interface IJsonBallot { + votes: BigNumberish[]; + nonce: BigNumberish; + voteOptionTreeDepth: BigNumberish; +} diff --git a/domainobjs/ts/verifyingKey.ts b/domainobjs/ts/verifyingKey.ts index f5c666a68c..afeee44627 100644 --- a/domainobjs/ts/verifyingKey.ts +++ b/domainobjs/ts/verifyingKey.ts @@ -1,14 +1,20 @@ import { G1Point, G2Point } from "maci-crypto"; +import type { IVkContractParams, IVkObjectParams } from "./types"; + /** * @notice A TS Class representing a zk-SNARK VerifyingKey */ export class VerifyingKey { - public alpha1: G1Point; - public beta2: G2Point; - public gamma2: G2Point; - public delta2: G2Point; - public ic: G1Point[]; + alpha1: G1Point; + + beta2: G2Point; + + gamma2: G2Point; + + delta2: G2Point; + + ic: G1Point[]; /** * Generate a new VerifyingKey @@ -31,7 +37,7 @@ export class VerifyingKey { * to the smart contract * @returns the object representation of this */ - public asContractParam() { + asContractParam(): IVkContractParams { return { alpha1: this.alpha1.asContractParam(), beta2: this.beta2.asContractParam(), @@ -46,17 +52,16 @@ export class VerifyingKey { * @param data the object representation * @returns a new VerifyingKey */ - public static fromContract(data: any): VerifyingKey { - const convertG2 = (point: any): G2Point => { - return new G2Point([BigInt(point.x[0]), BigInt(point.x[1])], [BigInt(point.y[0]), BigInt(point.y[1])]); - }; + static fromContract(data: IVkContractParams): VerifyingKey { + const convertG2 = (point: IVkContractParams["beta2"]): G2Point => + new G2Point([BigInt(point.x[0]), BigInt(point.x[1])], [BigInt(point.y[0]), BigInt(point.y[1])]); return new VerifyingKey( new G1Point(BigInt(data.alpha1.x), BigInt(data.alpha1.y)), convertG2(data.beta2), convertG2(data.gamma2), convertG2(data.delta2), - data.ic.map((c: any) => new G1Point(BigInt(c.x), BigInt(c.y))), + data.ic.map((c) => new G1Point(BigInt(c.x), BigInt(c.y))), ); } @@ -65,17 +70,14 @@ export class VerifyingKey { * @param vk the other verifying key * @returns whether this is equal to the other verifying key */ - public equals(vk: VerifyingKey): boolean { - let icEqual = this.ic.length === vk.ic.length; - + equals(vk: VerifyingKey): boolean { // Immediately return false if the length doesn't match - if (!icEqual) return false; - - // Each element in ic must match - for (let i = 0; i < this.ic.length; i++) { - icEqual = icEqual && this.ic[i].equals(vk.ic[i]); + if (this.ic.length !== vk.ic.length) { + return false; } + const icEqual = this.ic.every((ic, index) => ic.equals(vk.ic[index])); + return ( this.alpha1.equals(vk.alpha1) && this.beta2.equals(vk.beta2) && @@ -89,30 +91,29 @@ export class VerifyingKey { * Produce a copy of this verifying key * @returns the copy */ - public copy(): VerifyingKey { - const copyG2 = (point: any): G2Point => { - return new G2Point( + copy(): VerifyingKey { + const copyG2 = (point: G2Point): G2Point => + new G2Point( [BigInt(point.x[0].toString()), BigInt(point.x[1].toString())], [BigInt(point.y[0].toString()), BigInt(point.y[1].toString())], ); - }; return new VerifyingKey( new G1Point(BigInt(this.alpha1.x.toString()), BigInt(this.alpha1.y.toString())), copyG2(this.beta2), copyG2(this.gamma2), copyG2(this.delta2), - this.ic.map((c: any) => new G1Point(BigInt(c.x.toString()), BigInt(c.y.toString()))), + this.ic.map((c: G1Point) => new G1Point(BigInt(c.x.toString()), BigInt(c.y.toString()))), ); } /** * Deserialize into a VerifyingKey instance - * @param j the JSON representation + * @param json the JSON representation * @returns the VerifyingKey */ - public static fromJSON = (j: string): VerifyingKey => { - const data = JSON.parse(j); + static fromJSON = (json: string): VerifyingKey => { + const data = JSON.parse(json) as IVkObjectParams; return VerifyingKey.fromObj(data); }; @@ -121,7 +122,7 @@ export class VerifyingKey { * @param data the object representation * @returns the VerifyingKey */ - public static fromObj = (data: any): VerifyingKey => { + static fromObj = (data: IVkObjectParams): VerifyingKey => { const alpha1 = new G1Point(BigInt(data.vk_alpha_1[0]), BigInt(data.vk_alpha_1[1])); const beta2 = new G2Point( [BigInt(data.vk_beta_2[0][1]), BigInt(data.vk_beta_2[0][0])], @@ -135,7 +136,7 @@ export class VerifyingKey { [BigInt(data.vk_delta_2[0][1]), BigInt(data.vk_delta_2[0][0])], [BigInt(data.vk_delta_2[1][1]), BigInt(data.vk_delta_2[1][0])], ); - const ic = data.IC.map((ic: any) => new G1Point(BigInt(ic[0]), BigInt(ic[1]))); + const ic = data.IC.map(([x, y]) => new G1Point(BigInt(x), BigInt(y))); return new VerifyingKey(alpha1, beta2, gamma2, delta2, ic); }; diff --git a/domainobjs/tsconfig.json b/domainobjs/tsconfig.json index 8ccb9f07a7..cba918a45a 100644 --- a/domainobjs/tsconfig.json +++ b/domainobjs/tsconfig.json @@ -2,7 +2,32 @@ "extends": "../tsconfig.json", "compilerOptions": { "outDir": "./build", - "declaration": true + "declaration": true, + "allowJs": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "target": "ES2020", + "lib": ["es2020"], + "experimentalDecorators": true, + "strict": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "strictPropertyInitialization": true, + "strictNullChecks": true, + "module": "commonjs", + "moduleResolution": "node", + "resolveJsonModule": true, + "skipLibCheck": true, + "esModuleInterop": true, + "isolatedModules": true, + "forceConsistentCasingInFileNames": true, + "allowSyntheticDefaultImports": true, + "composite": true, + "incremental": true, + "declarationMap": true, + "sourceMap": true, + "stripInternal": true }, "include": ["./ts"] }