10 KiB
sidebar_position
| sidebar_position |
|---|
| 2 |
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
yarnpackage manager. -
Create a directory for the project and change to the new directory.
mkdir semaphore-example cd semaphore-example -
In your terminal, run
yarn initto 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
yarnto install Hardhat:yarn add hardhat --dev -
Use
yarnto runhardhatand 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 a Hardhat plugin for developers building zero-knowledge applications.
@semaphore-protocol/contractsprovides contracts to manage groups and verify Semaphore proofs on-chain.- JavaScript libraries help developers build zero-knowledge applications.
@semaphore-protocol/hardhatallows developers Hardhat tasks to deploy verifiers and Semaphore contracts.
To install these dependencies for your project, do the following:
-
Use
yarnto install@semaphore-protocol/contracts:yarn add @semaphore-protocol/contracts@2.6.1 -
Use
yarnto install the Semaphore JavaScript libraries and the Hardhat plugin:yarn add @semaphore-protocol/identity@2.6.1 @semaphore-protocol/group@2.6.1 @semaphore-protocol/proof@2.6.1 @semaphore-protocol/hardhat@0.1.0 --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.soltoGreeter.soland replace the content with the following://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
tasksfolder and add a./tasks/deploy.jsfile that contains the following:const { 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.jsfile, add the following:require("@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
yarnto install thehardhat-waffleplugin 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
./staticfolder.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.jstest file toGreeter.jsand replace the content with the following:const { 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
yarncommands 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.