Skip to content

Commit

Permalink
feat: make nullifier not leak identity between polls
Browse files Browse the repository at this point in the history
  • Loading branch information
ctrlc03 committed Dec 20, 2024
1 parent 8e1ab15 commit f1bd6df
Show file tree
Hide file tree
Showing 12 changed files with 41 additions and 19 deletions.
9 changes: 4 additions & 5 deletions packages/circuits/circom/anon/pollJoining.circom
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,11 @@ template PollJoining(stateTreeDepth) {
signal input stateRoot;
// The actual tree depth (might be <= stateTreeDepth) Used in BinaryMerkleRoot
signal input actualStateTreeDepth;
// The poll id
signal input pollId;

var computedNullifier = PoseidonHasher(1)([privKey]);
// Compute the nullifier (hash of private key and poll id)
var computedNullifier = PoseidonHasher(2)([privKey, pollId]);
nullifier === computedNullifier;

// User private to public key
Expand All @@ -69,8 +72,4 @@ template PollJoining(stateTreeDepth) {
// Check credits
var isCreditsValid = SafeLessEqThan(N_BITS)([credits, stateLeaf[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX]]);
isCreditsValid === 1;

// Check nullifier
var hashedPrivKey = PoseidonHasher(1)([privKey]);
hashedPrivKey === nullifier;
}
2 changes: 1 addition & 1 deletion packages/circuits/circom/circuits.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"file": "./anon/pollJoining",
"template": "PollJoining",
"params": [10],
"pubs": ["nullifier", "credits", "stateRoot", "pollPubKey"]
"pubs": ["nullifier", "credits", "stateRoot", "pollPubKey", "pollId"]
},
"ProcessMessages_10-20-2_test": {
"file": "./core/qv/processMessages",
Expand Down
1 change: 1 addition & 0 deletions packages/circuits/ts/__tests__/PollJoining.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ describe("Poll Joining circuit", function test() {
"credits",
"stateRoot",
"actualStateTreeDepth",
"pollId",
];

let circuit: WitnessTester<PollJoiningCircuitInputs>;
Expand Down
1 change: 1 addition & 0 deletions packages/circuits/ts/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export interface IPollJoiningInputs {
credits: bigint;
stateRoot: bigint;
actualStateTreeDepth: bigint;
pollId: bigint;
}

/**
Expand Down
16 changes: 10 additions & 6 deletions packages/cli/ts/commands/joinPoll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ const generateAndVerifyProof = async (
* @param credits Credits for voting
* @param pollPrivKey Poll's private key for the poll joining
* @param pollPubKey Poll's public key for the poll joining
* @param pollId Poll's id
* @returns stringified circuit inputs
*/
const joiningCircuitInputs = (
Expand All @@ -111,6 +112,7 @@ const joiningCircuitInputs = (
credits: bigint,
pollPrivKey: PrivKey,
pollPubKey: PubKey,
pollId: bigint,
): IPollJoiningCircuitInputs => {
// Get the state leaf on the index position
const { signUpTree: stateTree, stateLeaves } = signUpData;
Expand Down Expand Up @@ -146,7 +148,7 @@ const joiningCircuitInputs = (

// Create nullifier from private key
const inputNullifier = BigInt(maciPrivKey.asCircuitInputs());
const nullifier = poseidon([inputNullifier]);
const nullifier = poseidon([inputNullifier, pollId]);

// Get pll state tree's root
const stateRoot = stateTree.root;
Expand All @@ -167,6 +169,7 @@ const joiningCircuitInputs = (
credits,
stateRoot,
actualStateTreeDepth,
pollId,
};

return stringifyBigInts(circuitInputs) as unknown as IPollJoiningCircuitInputs;
Expand Down Expand Up @@ -208,19 +211,19 @@ export const joinPoll = async ({
logError("Invalid MACI private key");
}

if (pollId < 0) {
logError("Invalid poll id");
}

const userMaciPrivKey = PrivKey.deserialize(privateKey);
const userMaciPubKey = new Keypair(userMaciPrivKey).pubKey;
const nullifier = poseidon([BigInt(userMaciPrivKey.asCircuitInputs())]);
const nullifier = poseidon([BigInt(userMaciPrivKey.asCircuitInputs()), pollId]);

// Create poll public key from poll private key
const pollPrivKeyDeserialized = PrivKey.deserialize(pollPrivKey);
const pollKeyPair = new Keypair(pollPrivKeyDeserialized);
const pollPubKey = pollKeyPair.pubKey;

if (pollId < 0) {
logError("Invalid poll id");
}

const maciContract = MACIFactory.connect(maciAddress, signer);
const pollContracts = await maciContract.getPoll(pollId);

Expand Down Expand Up @@ -325,6 +328,7 @@ export const joinPoll = async ({
loadedCreditBalance!,
pollPrivKeyDeserialized,
pollPubKey,
pollId,
) as unknown as CircuitInputs;
}

Expand Down
3 changes: 2 additions & 1 deletion packages/contracts/contracts/MACI.sol
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,8 @@ contract MACI is IMACI, DomainObjs, Params, Utilities {
_messageBatchSize,
_coordinatorPubKey,
extContracts,
emptyBallotRoots[voteOptionTreeDepth - 1]
emptyBallotRoots[voteOptionTreeDepth - 1],
pollId
);

address mp = messageProcessorFactory.deploy(_verifier, _vkRegistry, p, msg.sender, _mode);
Expand Down
12 changes: 10 additions & 2 deletions packages/contracts/contracts/Poll.sol
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ contract Poll is Params, Utilities, SnarkCommon, IPoll {
/// @notice Poll voting nullifier
mapping(uint256 => bool) private pollNullifier;

/// @notice The Id of this poll
uint256 public immutable pollId;

error VotingPeriodOver();
error VotingPeriodNotOver();
error PollAlreadyInit();
Expand Down Expand Up @@ -125,13 +128,15 @@ contract Poll is Params, Utilities, SnarkCommon, IPoll {
/// @param _coordinatorPubKey The coordinator's public key
/// @param _extContracts The external contracts
/// @param _emptyBallotRoot The root of the empty ballot tree
/// @param _pollId The poll id
constructor(
uint256 _duration,
TreeDepths memory _treeDepths,
uint8 _messageBatchSize,
PubKey memory _coordinatorPubKey,
ExtContracts memory _extContracts,
uint256 _emptyBallotRoot
uint256 _emptyBallotRoot,
uint256 _pollId
) payable {
// check that the coordinator public key is valid
if (!CurveBabyJubJub.isOnCurve(_coordinatorPubKey.x, _coordinatorPubKey.y)) {
Expand All @@ -156,6 +161,8 @@ contract Poll is Params, Utilities, SnarkCommon, IPoll {
deployTime = block.timestamp;
// store the empty ballot root
emptyBallotRoot = _emptyBallotRoot;
// store the poll id
pollId = _pollId;
}

/// @notice A modifier that causes the function to revert if the voting period is
Expand Down Expand Up @@ -350,13 +357,14 @@ contract Poll is Params, Utilities, SnarkCommon, IPoll {
uint256 _index,
PubKey calldata _pubKey
) public view returns (uint256[] memory publicInputs) {
publicInputs = new uint256[](5);
publicInputs = new uint256[](6);

publicInputs[0] = _pubKey.x;
publicInputs[1] = _pubKey.y;
publicInputs[2] = _nullifier;
publicInputs[3] = _voiceCreditBalance;
publicInputs[4] = extContracts.maci.getStateRootOnIndexedSignUp(_index);
publicInputs[5] = pollId;
}

/// @inheritdoc IPoll
Expand Down
6 changes: 4 additions & 2 deletions packages/contracts/contracts/PollFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ contract PollFactory is Params, DomainObjs, IPollFactory {
uint8 _messageBatchSize,
DomainObjs.PubKey calldata _coordinatorPubKey,
Params.ExtContracts calldata _extContracts,
uint256 _emptyBallotRoot
uint256 _emptyBallotRoot,

Check warning

Code scanning / Slither

Conformance to Solidity naming conventions Warning

uint256 _pollId

Check warning

Code scanning / Slither

Conformance to Solidity naming conventions Warning

) public virtual returns (address pollAddr) {
// deploy the poll
Poll poll = new Poll(
Expand All @@ -30,7 +31,8 @@ contract PollFactory is Params, DomainObjs, IPollFactory {
_messageBatchSize,
_coordinatorPubKey,
_extContracts,
_emptyBallotRoot
_emptyBallotRoot,
_pollId
);

// init Poll
Expand Down
4 changes: 3 additions & 1 deletion packages/contracts/contracts/interfaces/IPollFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ interface IPollFactory {
/// @param _coordinatorPubKey The coordinator's public key
/// @param _extContracts The external contracts interface references
/// @param _emptyBallotRoot The root of the empty ballot tree
/// @param _pollId The poll id
/// @return The deployed Poll contract
function deploy(
uint256 _duration,
Params.TreeDepths calldata _treeDepths,
uint8 _messageBatchSize,
DomainObjs.PubKey calldata _coordinatorPubKey,
Params.ExtContracts calldata _extContracts,
uint256 _emptyBallotRoot
uint256 _emptyBallotRoot,
uint256 _pollId
) external returns (address);
}
1 change: 1 addition & 0 deletions packages/contracts/tests/PollFactory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ describe("pollFactory", () => {
coordinatorPubKey.asContractParam(),
extContracts,
emptyBallotRoot,
0n,
);
const receipt = await tx.wait();
expect(receipt?.status).to.eq(1);
Expand Down
4 changes: 3 additions & 1 deletion packages/core/ts/Poll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ export class Poll implements IPoll {
* @param treeDepths - The depths of the trees used in the poll.
* @param batchSizes - The sizes of the batches used in the poll.
* @param maciStateRef - The reference to the MACI state.
* @param pollId - The poll id
*/
constructor(
pollEndTimestamp: bigint,
Expand Down Expand Up @@ -471,7 +472,7 @@ export class Poll implements IPoll {

// Create nullifier from private key
const inputNullifier = BigInt(maciPrivKey.asCircuitInputs());
const nullifier = poseidon([inputNullifier]);
const nullifier = poseidon([inputNullifier, this.pollId]);

// Get pll state tree's root
const stateRoot = this.stateTree!.root;
Expand All @@ -490,6 +491,7 @@ export class Poll implements IPoll {
credits,
stateRoot,
actualStateTreeDepth,
pollId: this.pollId,
};

return stringifyBigInts(circuitInputs) as unknown as IPollJoiningCircuitInputs;
Expand Down
1 change: 1 addition & 0 deletions packages/core/ts/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ export interface IPollJoiningCircuitInputs {
credits: string;
stateRoot: string;
actualStateTreeDepth: string;
pollId: string;
}
/**
* An interface describing the circuit inputs to the ProcessMessage circuit
Expand Down

0 comments on commit f1bd6df

Please sign in to comment.