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 contracts and JS libraries
- Create the Semaphore contract
- Create Semaphore IDs
- Create a Hardhat task that deploys your contract
- Deploy your contract to a local network
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-exampleIn 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 basic sample project:yarn hardhat
# At the prompt, select "Create a basic sample project"
# and then enter through the prompts.
Install Semaphore contracts and JS libraries​
@semaphore-protocol/contracts
provides a base contract that verifies
Semaphore proofs.
Semaphore also provides JavaScript libraries that help developers
build zero-knowledge applications.
To install these dependencies for your project, do the following:
Use
yarn
to install@semaphore-protocol/contracts
:yarn add @semaphore-protocol/contracts
For more detail about Semaphore base contracts, see Contracts. To view the source, see Contracts in the Semaphore repository.
Use
yarn
to install the JS libraries:yarn add @semaphore-protocol/identity @semaphore-protocol/group @semaphore-protocol/proof --dev
For more information about the JS libraries, see the semaphore.js repository.
Create the Semaphore contract​
Create a Greeters
contract that imports and extends the Semaphore base contract:
In
./contracts
, renameGreeter.sol
toGreeters.sol
.Replace the contents of
Greeters.sol
with the following:./semaphore-example/contracts/Greeters.sol//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@semaphore-protocol/contracts/interfaces/IVerifier.sol";
import "@semaphore-protocol/contracts/base/SemaphoreCore.sol";
/// @title Greeters contract.
/// @dev The following code is just a example to show how Semaphore con be used.
contract Greeters is SemaphoreCore {
// A new greeting is published every time a user's proof is validated.
event NewGreeting(bytes32 greeting);
// Greeters are identified by a Merkle root.
// The off-chain Merkle tree contains the greeters' identity commitments.
uint256 public greeters;
// The external verifier used to verify Semaphore proofs.
IVerifier public verifier;
constructor(uint256 _greeters, address _verifier) {
greeters = _greeters;
verifier = IVerifier(_verifier);
}
// Only users who create valid proofs can greet.
// The external nullifier is in this example the root of the Merkle tree.
function greet(
bytes32 _greeting,
uint256 _nullifierHash,
uint256[8] calldata _proof
) external {
_verifyProof(_greeting, greeters, _nullifierHash, greeters, _proof, verifier);
// Prevent double-greeting (nullifierHash = hash(root + identityNullifier)).
// Every user can greet once.
_saveNullifierHash(_nullifierHash);
emit NewGreeting(_greeting);
}
}
Create Semaphore IDs​
Semaphore IDs (i.e. identity commitments) represent user identities and are the leaves of the Merkle trees in the protocol.
Create a ./static
folder and add a ./static/identityCommitments.json
file that
contains the following array of IDs:
[
"9426253249246138013650573474062059446203468399013007463704855436559640562175",
"6200634377081441056179822649025268043304989981899916286941956069781421654881",
"19706772421195815860043593475869058320994241404138740034486179990871964981523"
]
info
To generate the IDs for this example, we used @semaphore-protocol/identity
.
We used Metamask to sign messages with the first 3 Ethereum accounts
of the Hardhat dev wallet, and then we used those messages to generate Semaphore deterministic identities.
Create a Hardhat task that deploys your contract​
Hardhat lets you write tasks
that automate building and deploying smart contracts and dApps.
To create a task that deploys the Greeters
contract, do the following:
Use
yarn
to installhardhat-dependency-compiler
:yarn add hardhat-dependency-compiler --dev
hardhat-dependency-compiler
compiles Solidity contracts and dependencies.Create a
tasks
folder and add a./tasks/deploy.js
file that contains the following:./tasks/deploy.jsconst { Group } = require("@semaphore-protocol/group")
const identityCommitments = require("../static/identityCommitments.json")
const { task, types } = require("hardhat/config")
task("deploy", "Deploy a Greeters contract")
.addOptionalParam("logs", "Print the logs", true, types.boolean)
.setAction(async ({ logs }, { ethers }) => {
const VerifierContract = await ethers.getContractFactory("Verifier20")
const verifier = await VerifierContract.deploy()
await verifier.deployed()
logs && console.log(`Verifier contract has been deployed to: ${verifier.address}`)
const GreetersContract = await ethers.getContractFactory("Greeters")
const group = new Group()
group.addMembers(identityCommitments)
const greeters = await GreetersContract.deploy(group.root, verifier.address)
await greeters.deployed()
logs && console.log(`Greeters contract has been deployed to: ${greeters.address}`)
return greeters
})In your
hardhat.config.js
file, add the following:./hardhat.config.jsrequire("@nomiclabs/hardhat-waffle")
require("hardhat-dependency-compiler")
require("./tasks/deploy") // Your deploy task.
module.exports = {
solidity: "0.8.4",
dependencyCompiler: {
// It allows Hardhat to compile the external Verifier.sol contract.
paths: ["@semaphore-protocol/contracts/verifiers/Verifier20.sol"]
}
}
Test your smart 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 '[email protected]^3.0.0' \
@nomiclabs/hardhat-ethers '[email protected]^5.0.0' chaiDownload the Semaphore zk files and copy them to the
./static
folder. Your application and tests must pass these static files to Semaphore to create zero-knowledge proofs.cd static
wget http://www.trusted-setup-pse.org/semaphore/20/semaphore.zkey
wget http://www.trusted-setup-pse.org/semaphore/20/semaphore.wasmReplace the contents of
./test/sample-test.js
with the following test:./test/sample-test.jsconst { Identity } = require("@semaphore-protocol/identity")
const { Group } = require("@semaphore-protocol/group")
const { generateProof, packToSolidityProof } = require("@semaphore-protocol/proof")
const identityCommitments = require("../static/identityCommitments.json")
const { expect } = require("chai")
const { run, ethers } = require("hardhat")
describe("Greeters", function () {
let contract
let signers
before(async () => {
contract = await run("deploy", { logs: false })
signers = await ethers.getSigners()
})
describe("# greet", () => {
const wasmFilePath = "./static/semaphore.wasm"
const zkeyFilePath = "./static/semaphore.zkey"
it("Should greet", async () => {
const greeting = "Hello world"
const bytes32Greeting = ethers.utils.formatBytes32String(greeting)
const message = await signers[0].signMessage("Sign this message to create your identity!")
const identity = new Identity(message)
const group = new Group()
group.addMembers(identityCommitments)
const fullProof = await generateProof(identity, group, group.root, greeting, {
wasmFilePath,
zkeyFilePath
})
const solidityProof = packToSolidityProof(fullProof.proof)
const transaction = contract.greet(
bytes32Greeting,
fullProof.publicSignals.nullifierHash,
solidityProof
)
await expect(transaction).to.emit(contract, "NewGreeting").withArgs(bytes32Greeting)
})
})
})Run the following
yarn
commands to compile and test your contract:yarn hardhat compile
yarn hardhat test
Deploy your contract to a local network​
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 --network localhost # In another tab.
For a more complete demo that provides a starting point for your dApp, see semaphore-boilerplate.