Welcome to the scroll-semaphore
repository! This project demonstrates how to use Semaphore on the Scroll network to prove membership in a group without revealing your identity. You'll learn how to deploy key contracts, interact with them, and connect a frontend app to create a privacy-preserving voting system.
Check out the demo
- Introduction
- Prerequisites
- Setup
- Deploying Key Contracts
- Running the Frontend App
- Interacting with the App
- Understanding the Code
- Conclusion
- Resources
In the rapidly evolving landscape of blockchain technology, privacy and scalability remain two critical challenges. Semaphore is a zero-knowledge protocol that enables users to prove membership in a group without revealing their identity. Scroll is a zkEVM-based zkRollup on Ethereum that enhances scalability without compromising security or decentralization.
Combining Semaphore with Scroll opens up new possibilities for building scalable and privacy-preserving applications on Ethereum. This project demonstrates how to integrate Semaphore with Scroll to create a system where users can authenticate and interact anonymously.
- Node.js and npm installed
- Metamask or another Ethereum-compatible wallet
- Basic knowledge of Solidity and smart contract deployment
- Access to the Scroll Sepolia Testnet
First, get Scroll Sepolia ETH by bridging your Ethereum Sepolia ETH:
- Visit the Scroll Sepolia Bridge.
- Bridge your Ethereum Sepolia ETH to Scroll Sepolia ETH.
- The process may take up to 20 minutes (typically around 12 minutes).
For deployment and RPC node-related tasks, we'll use MultiBaas by Curvegrid:
- Sign up for a free account.
- Create up to two deployments on Scroll Mainnet and Testnet (or other EVM chains).
Semaphore requires three key contracts:
Semaphore
SemaphoreVerifier
PoseidonT3
At the time of writing, these contracts might not be available on the Scroll Testnet. You may need to deploy them manually.
SemaphoreVerifier
:0x76C994dA048f14F4153Ac550cA6f6b327fCE9180
PoseidonT3
:0x5A7242de32803bC5329Ca287167eE726E53b219A
Semaphore
:0x0303e10025D7578aC8e4fcCD0249622ac1D17B82
-
Access Deployment Dashboard:
- Log in to your MultiBaas dashboard.
- Navigate to your deployment.
-
Deploy Contract:
- Go to
Contracts
>On-Chain
. - Click the
+
(plus) button. - Choose
Deploy Contract
. - Select
Contract from Compilation Artifact
. - Upload
artifacts/contracts/TestSemaphore.json
from this repository. - Label the contract (e.g.,
semaphore
) and assign a version number. - Ensure
Sync Events
is checked. - Set the
_verifier
constructor argument to theSemaphoreVerifier
address (0x76C994...
). - Click
Deploy
. - Confirm the transaction in Metamask when prompted.
- Go to
-
Alternative Method:
- Instead of uploading the contract artifact, you can fetch the contract's bytecode and ABI if the contract is verified.
- Use the official
Semaphore
contract address to link it in MultiBaas.
We'll deploy the SimpleVoting
contract, which integrates with Semaphore.
-
Prepare Constructor Arguments:
_numChoices
:4
semaphoreAddress
: Address of your deployedSemaphore
contract (e.g.,0x0303e1...
)._secretCodeHash
: Keccak256 hash of the secret code"GM!"
(0xc87a2838ff5cbcb7515eef22d409b3271b26f101f3b1a51873086460417c4454
).
-
Deploy Contract:
- Use MultiBaas to deploy the
SimpleVoting
contract with the above arguments. - Ensure
Sync Events
is checked. - Confirm the transaction in Metamask.
- Use MultiBaas to deploy the
-
Obtain API Key and Configure CORS:
- Navigate to
Admin
>API Keys
. - Click
+ New Key
. - Label the key and select
DApp User
, then clickCreate
. - Go to
Admin
>CORS Origins
. - Click
+ Add Origin
. - Input
http://localhost:3000
for local development.
- Navigate to
git clone https://github.com/tawago/scroll-semaphore.git
cd scroll-semaphore/frontend
cp .env.template .env.development
-
Open
.env.development
and update the following variables:NEXT_PUBLIC_API_KEY
: Your MultiBaas API key.NEXT_PUBLIC_DEPLOYMENT_URL
: Your MultiBaas deployment URL.
yarn install
yarn dev
- Open http://localhost:3000 in your browser.
- Click the
Create
button to generate your Semaphore Identity. - This creates a unique identity commitment stored locally.
- Ensure your wallet (e.g., Metamask) is connected to the Scroll Sepolia network.
- Enter the secret code
GM!
in the text field. - Click
Join
and confirm the transaction in your wallet.
- Once you're a group member, you can cast a vote anonymously.
- Select an option and click
Vote
. - The app generates a Semaphore proof to verify your membership without revealing your identity.
To generate a Semaphore proof, you need:
- Your identity.
- The Merkle tree proof of your commitment.
- A message (can be empty).
- A scope (used to prevent double-signaling).
import { poseidon2 } from "poseidon-lite";
import { LeanIMT } from "@zk-kit/lean-imt";
export async function generateMerkleProof(
commitmentsArray: bigint[],
index: number
) {
const hash = (a, b) => poseidon2([a, b]);
// Initialize the Merkle tree
const tree = new LeanIMT<bigint>(hash);
// Insert the commitments into the tree
for (const commitment of commitmentsArray) {
tree.insert(commitment);
}
// Generate the proof for the leaf at the given index
const proof = tree.generateProof(index);
// The proof object already has the structure we need
const merkleProof = {
root: proof.root,
leaf: proof.leaf,
index: proof.index,
siblings: proof.siblings,
};
return merkleProof;
}
You need all group commitments to generate the Merkle proof. There are two methods:
const getCommitmentsFromMemberAddedEvents = async (): Promise<Array<bigint> | null> => {
try {
const eventSignature = "MemberAdded(uint256,uint256,uint256,uint256)";
const response = await eventsApi.listEvents(
undefined,
undefined,
undefined,
undefined,
undefined,
false,
chain,
semaphoreAddressLabel,
semaphoreContractLabel,
eventSignature,
50
);
const events: Event[] = response.data.result.filter(
(event) => event.transaction.contract.addressLabel === votingAddressLabel
);
const commitments = events
.sort(
(a, b) =>
new Date(a.triggeredAt).getTime() - new Date(b.triggeredAt).getTime()
)
.map((item) => item.event.inputs[2].value);
return commitments;
} catch (err) {
console.error("Error getting member added events:", err);
return null;
}
};
-
Set up an event query in MultiBaas UI:
- Go to
Blockchain
>Event Queries
. - Click the
+
button to create a new query. - Name the query
commitments
. - Add the event
MemberAdded(uint256,uint256,uint256,uint256)
. - Add event fields:
groupId
,identityCommitment
,triggered_at
. - Add a filter where
groupId
equals your group's ID. - Save the query.
- Go to
-
Fetch commitments using the query:
const queryLabel = "commitments";
const response = await eventQueriesApi.executeEventQuery(queryLabel);
const commitments = response.data.result.rows.map(
(row: any) => row.identitycommitment
);
return commitments;
const castVote = useCallback(async (choice: string): Promise<SendTransactionParameters> => {
if (!identity) throw Error("No identity");
const scope = groupId;
// Fetch commitments using one of the methods
const commitments = await _getCommitmentsFromQuery();
if (!commitments?.length) throw Error("No members in this group");
const index = await callContractFunction(
"indexOf",
[groupId, identity?.commitment.toString()],
true
);
const merkleProof = await generateMerkleProof(commitments, Number(index));
const proof = await generateSemaphoreProof(identity, merkleProof, choice, scope);
console.log("generateSemaphoreProof", proof);
return await callContractFunction("vote", [
proof.merkleTreeDepth,
proof.merkleTreeRoot,
proof.nullifier,
proof.message,
proof.points,
]);
}, [callContractFunction, groupId, identity]);
Interacting with the Semaphore contract and creating a smart contract for a membership application is intuitive. The main challenge may be deploying the key contracts if they're not available on your target network.
Future enhancements could include:
- Implementing the secret code in a zero-knowledge proof manner.
- Adding features to open new ballots with unique scopes.
- Using MACI to create a more robust anonymous voting system.
- Adding delegation mechanisms.
- Getting Started with Semaphore: Semaphore Documentation
- Semaphore Repository: Semaphore GitHub
- Official Boilerplate: Semaphore Boilerplate
- Sample Voting App Repository: scroll-semaphore GitHub
- The Graph Endpoint for Scroll Semaphore: GraphQL API
- Scroll Sepolia Bridge: Scroll Bridge
- Curvegrid Console: MultiBaas Console