Quick setup
Set up a new Hardhat project with Semaphore. Learn how to create and test an Ethereum smart contract that uses zero-knowledge proofs to verify membership.
To check out the code used in this guide, visit the quick-setup repository.
- Create a Node.js project
- Install Hardhat
- Install Semaphore packages
- Create the Semaphore contract
- Create a Hardhat task
- Test your contracts
- Deploy your contract
Create a Node.js project
-
Follow the Node.js LTS version instructions to install
node
(Hardhat may not work with Node.js Current). -
Follow the Yarn instructions to download and install the
yarn
package manager. -
Create a directory for the project and change to the new directory.
mkdir semaphore-example
cd semaphore-example -
In your terminal, run
yarn init
to initialize the Node.js project.
Install Hardhat
Hardhat is a development environment you can use to compile, deploy, test, and debug Ethereum software. Hardhat includes the Hardhat Network, a local Ethereum network for development.
-
Use
yarn
to install Hardhat:yarn add hardhat --dev
-
Use
yarn
to runhardhat
and create a JavaScript project:yarn hardhat
# At the prompt, select "Create a JavaScript project"
# and then enter through the prompts.
Install Semaphore packages
Semaphore provides contracts, JavaScript libraries and an Hardhat plugin for developers building zero-knowledge applications.
@semaphore-protocol/contracts
provides contracts to manage groups and verify Semaphore proofs on-chain.- JavaScript libraries help developers build zero-knowledge applications.
@semaphore-protocol/hardhat
allow developers Hardhat tasks to deploy verifiers and Semaphore contracts.
To install these dependencies for your project, do the following:
-
Use
yarn
to install@semaphore-protocol/contracts
:yarn add @semaphore-protocol/[email protected]
-
Use
yarn
to install the Semaphore JavaScript libraries and the Hardhat plugin:yarn add @semaphore-protocol/[email protected] @semaphore-protocol/[email protected] @semaphore-protocol/[email protected] @semaphore-protocol/[email protected] --dev
For more detail about Semaphore contracts, see Contracts. To view the source of our packages, see the semaphore repository.
Create the Semaphore contract
Create a Greeter
contract that uses the Semaphore.sol
contract:
-
Rename
Lock.sol
toGreeter.sol
and replace the content with the following:./contracts/Greeter.sol//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@semaphore-protocol/contracts/interfaces/ISemaphore.sol";
/// @title Greeter contract.
/// @dev The following code is just an example to show how Semaphore can be used.
contract Greeter {
event NewGreeting(bytes32 greeting);
event NewUser(uint256 identityCommitment, bytes32 username);
ISemaphore public semaphore;
uint256 groupId;
mapping(uint256 => bytes32) users;
constructor(address semaphoreAddress, uint256 _groupId) {
semaphore = ISemaphore(semaphoreAddress);
groupId = _groupId;
semaphore.createGroup(groupId, 20, 0, address(this));
}
function joinGroup(uint256 identityCommitment, bytes32 username) external {
semaphore.addMember(groupId, identityCommitment);
users[identityCommitment] = username;
emit NewUser(identityCommitment, username);
}
function greet(
bytes32 greeting,
uint256 merkleTreeRoot,
uint256 nullifierHash,
uint256[8] calldata proof
) external {
semaphore.verifyProof(groupId, merkleTreeRoot, greeting, nullifierHash, groupId, proof);
emit NewGreeting(greeting);
}
}
Create a Hardhat task
Hardhat lets you write tasks
that automate building and deploying smart contracts and dApps.
To create a task that deploys the Greeter
contract, do the following:
-
Create a
tasks
folder and add a./tasks/deploy.js
file that contains the following:./tasks/deploy.jsconst { task, types } = require("hardhat/config")
task("deploy", "Deploy a Greeter contract")
.addOptionalParam("semaphore", "Semaphore contract address", undefined, types.address)
.addParam("group", "Group identifier", 42, types.int)
.addOptionalParam("logs", "Print the logs", true, types.boolean)
.setAction(async ({ logs, semaphore: semaphoreAddress, group: groupId }, { ethers, run }) => {
if (!semaphoreAddress) {
const { address: verifierAddress } = await run("deploy:verifier", { logs, merkleTreeDepth: 20 })
const { address } = await run("deploy:semaphore", {
logs,
verifiers: [
{
merkleTreeDepth: 20,
contractAddress: verifierAddress
}
]
})
semaphoreAddress = address
}
const Greeter = await ethers.getContractFactory("Greeter")
const greeter = await Greeter.deploy(semaphoreAddress, groupId)
await greeter.deployed()
if (logs) {
console.log(`Greeter contract has been deployed to: ${greeter.address}`)
}
return greeter
}) -
In your
hardhat.config.js
file, add the following:./hardhat.config.jsrequire("@nomiclabs/hardhat-waffle")
require("@semaphore-protocol/hardhat")
require("./tasks/deploy") // Your deploy task.
module.exports = {
solidity: "0.8.4"
}
Test your contract
hardhat-waffle
lets you write tests with the Waffle test framework
and Chai assertions.
-
Use
yarn
to install thehardhat-waffle
plugin and dependencies for smart contract tests:yarn add -D @nomiclabs/hardhat-waffle 'ethereum-waffle@^3.0.0' \
@nomiclabs/hardhat-ethers 'ethers@^5.0.0' chai -
Download the Semaphore zk trusted setup files and copy them to the
./static
folder.cd static
wget http://www.trusted-setup-pse.org/semaphore/20/semaphore.zkey
wget http://www.trusted-setup-pse.org/semaphore/20/semaphore.wasmLearn more about trusted setup files.
-
Rename the
Lock.js
test file toGreeter.js
and replace the content with the following:./test/Greeter.jsconst { Identity } = require("@semaphore-protocol/identity")
const { Group } = require("@semaphore-protocol/group")
const { generateProof, packToSolidityProof, verifyProof } = require("@semaphore-protocol/proof")
const { expect } = require("chai")
const { run, ethers } = require("hardhat")
describe("Greeter", function () {
let greeter
const users = []
const groupId = 42
const group = new Group()
before(async () => {
greeter = await run("deploy", { logs: false, group: groupId })
users.push({
identity: new Identity(),
username: ethers.utils.formatBytes32String("anon1")
})
users.push({
identity: new Identity(),
username: ethers.utils.formatBytes32String("anon2")
})
group.addMember(users[0].identity.generateCommitment())
group.addMember(users[1].identity.generateCommitment())
})
describe("# joinGroup", () => {
it("Should allow users to join the group", async () => {
for (let i = 0; i < group.members.length; i++) {
const transaction = greeter.joinGroup(group.members[i], users[i].username)
await expect(transaction).to.emit(greeter, "NewUser").withArgs(group.members[i], users[i].username)
}
})
})
describe("# greet", () => {
const wasmFilePath = "./static/semaphore.wasm"
const zkeyFilePath = "./static/semaphore.zkey"
it("Should allow users to greet", async () => {
const greeting = ethers.utils.formatBytes32String("Hello World")
const fullProof = await generateProof(users[1].identity, group, groupId, greeting, {
wasmFilePath,
zkeyFilePath
})
const solidityProof = packToSolidityProof(fullProof.proof)
const transaction = greeter.greet(
greeting,
fullProof.publicSignals.merkleRoot,
fullProof.publicSignals.nullifierHash,
solidityProof
)
await expect(transaction).to.emit(greeter, "NewGreeting").withArgs(greeting)
})
})
}) -
Run the following
yarn
commands to compile and test your contract:yarn hardhat compile
yarn hardhat test
Deploy your contract
To deploy your contract in a local Hardhat network (and use it in your dApp), run the following yarn
commands:
yarn hardhat node
yarn hardhat deploy --group 42 --network localhost # In another tab.
For a more complete demo that provides a starting point for your dApp, see semaphore-boilerplate.