Skip to main content
Version: V2

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.

  1. Create a Node.js project
  2. Install Hardhat
  3. Install Semaphore contracts and JavaScript libraries
  4. Create the Semaphore contract
  5. Create Semaphore IDs
  6. Create a Hardhat task that deploys your contract
  7. Deploy your contract to a local network

Create a Node.js project

  1. Follow the Node.js LTS version instructions to install node (Hardhat may not work with Node.js Current).

  2. Follow the Yarn instructions to download and install the yarn package manager.

  3. Create a directory for the project and change to the new directory.

    mkdir semaphore-example
    cd semaphore-example
  4. 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.

  1. Use yarn to install Hardhat:

    yarn add hardhat --dev
  2. Use yarn to run hardhat 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 JavaScript libraries

Semaphore provides contracts and JavaScript libraries for developers building zero-knowledge applications.

  • @semaphore-protocol/contracts provides a base contract that verifies Semaphore proofs on-chain.
  • JavaScript libraries help developers build zero-knowledge applications.

To install these dependencies for your project, do the following:

  1. 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.

  2. Use yarn to install the Semaphore JavaScript libraries:

    yarn add @semaphore-protocol/identity @semaphore-protocol/group @semaphore-protocol/proof --dev

    For more information about the Semaphore JavaScript libraries, see the semaphore.js repository.

Create the Semaphore contract

Create a Greeters contract that imports and extends the Semaphore base contract:

  1. In ./contracts, rename Greeter.sol to Greeters.sol.

  2. Replace the contents of Greeters.sol with the following:

    //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.

    emit NewGreeting(_greeting);

Create Semaphore IDs

Semaphore IDs - also known as identity commitments - represent user identities.

Create a ./static folder and add a ./static/identityCommitments.json file that contains the following array of IDs:


To generate the Semaphore IDs for this example, we used @semaphore-protocol/identity. We used Metamask to sign messages with the first three Ethereum accounts of the Hardhat dev wallet, and then we used those messages to generate Semaphore deterministic identities.

In the Semaphore protocol, a group is an incremental Merkle tree. Semaphore IDs are tree leaves.

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:

  1. Use yarn to install hardhat-dependency-compiler:

    yarn add hardhat-dependency-compiler --dev

    hardhat-dependency-compiler compiles Solidity contracts and dependencies.

  2. Create a tasks folder and add a ./tasks/deploy.js file that contains the following:

    const { Group } = require("@semaphore-protocol/group")
    const identityCommitments = require("../static/identityCommitments.json")
    const { task, types } = require("hardhat/config")

    * The `task.setAction` function exposes the `ethers` Javascript library for interacting with Ethereum.
    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()


    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
  3. In your hardhat.config.js file, add the following:


    /** Import your deploy task */

    module.exports = {
    solidity: "0.8.4",
    dependencyCompiler: {
    /** 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.

  1. Use yarn to install the hardhat-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' chai
  2. Download the Semaphore zk trusted setup files and copy them to the ./static folder.

    cd static

    Learn more about trusted setup files.

  3. Replace the contents of ./test/sample-test.js with the following test:

    const { 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")

    /* Import the Ethers.js JavaScript library for interacting with Ethereum. */
    const { run, ethers } = require("hardhat")

    describe("Greeters", function () {
    let contract
    let signers

    before(async () => {
    contract = await run("deploy", { logs: false })

    * In Ethers.js, a Signer object represents an Ethereum Account
    * that you can use to sign messages and transactions, and then send
    * signed transactions to the Ethereum Network to execute
    * state-changing operations.
    signers = await ethers.getSigners()

    describe("# greet", () => {
    /** Use the trusted setup files. **/
    const wasmFilePath = "./static/semaphore.wasm"
    const zkeyFilePath = "./static/semaphore.zkey"

    it("Should greet", async () => {
    const greeting = "Hello world"
    const bytes32Greeting = ethers.utils.formatBytes32String(greeting)

    * In Ethers.js, Signer.signMessage returns a Promise which resolves to the Raw Signature of a message.
    * The following code gets the first (Signer) account and assigns the signature Promise to the message variable.
    const message = await signers[0].signMessage("Sign this message to create your identity!")
    const identity = new Identity(message)

    const group = new Group()


    const fullProof = await generateProof(identity, group, group.root, greeting, {
    const solidityProof = packToSolidityProof(fullProof.proof)

    const transaction = contract.greet(

    await expect(transaction).to.emit(contract, "NewGreeting").withArgs(bytes32Greeting)
  4. 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.