Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: make nullifier not leak identity between polls #1974

Merged
merged 1 commit into from
Dec 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
uint8 _messageBatchSize,
DomainObjs.PubKey calldata _coordinatorPubKey,
Params.ExtContracts calldata _extContracts,
uint256 _emptyBallotRoot
uint256 _emptyBallotRoot,
Dismissed Show dismissed Hide dismissed
uint256 _pollId
Dismissed Show dismissed Hide dismissed
) public virtual returns (address pollAddr) {
// deploy the poll
Poll poll = new Poll(
Expand All @@ -30,7 +31,8 @@
_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
Loading