7.4 KiB
sidebar_position
| sidebar_position |
|---|
| 2 |
Quick setup
The following setup will allow you to create a simple Hardhat project with Semaphore (contract and tests). If you need a more complete demo try semaphore-boilerplate. It can be a good starting point to create your DApp.
:::info Visit the semaphore-quick-setup repository to get a full view of the code used below. :::
Create a Node.js project
- Download and install Node.js/NPM and Yarn.
- Create your project:
$ mkdir semaphore-example
$ cd semaphore-example
$ yarn init
Install Hardhat and the Semaphore packages
- Install Hardhat and create a basic sample project:
$ yarn add hardhat --dev
$ yarn hardhat # Create a basic sample project and confirm the other requests.
:::tip Feel free to create more complex sample projects if you are an advanced Hardhat user. :::
- Install the Semaphore contracts and its ZK-kit libraries:
$ yarn add @appliedzkp/semaphore-contracts
$ yarn add @zk-kit/identity @zk-kit/protocols --dev
Create your contract
Rename Greeter.sol to Greeters.sol and insert the following code:
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
import "@appliedzkp/semaphore-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(string greeting);
// Greeters are identified by a Merkle root.
// The offchain Merkle tree contains the greeters' identity commitments.
uint256 public greeters;
constructor(uint256 _greeters) {
greeters = _greeters;
}
// Only users who create valid proofs can greet.
// The external nullifier is in this example the root of the Merkle tree.
function greet(
string calldata _greeting,
uint256 _nullifierHash,
uint256[8] calldata _proof
) external {
require(_isValidProof(_greeting, greeters, _nullifierHash, greeters, _proof), "Greeters: the proof is not valid");
// Prevent double-greeting (nullifierHash = hash(root + identityNullifier)).
// Every user can greet once.
_saveNullifierHash(_nullifierHash);
emit NewGreeting(_greeting);
}
}
Create some identity commitments
Identity commitments are used as the leaves of the Merkle trees used in the protocol and represent the identity of the users. Create a static folder and add the following file:
[
"9426253249246138013650573474062059446203468399013007463704855436559640562175",
"6200634377081441056179822649025268043304989981899916286941956069781421654881",
"19706772421195815860043593475869058320994241404138740034486179990871964981523"
]
:::info
The previous identity commitments have been generated using @zk-kit/identity (with a message strategy) and Metamask for signing the messages with the first 3 Ethereum accounts of the Hardhat dev wallet.
:::
Create a Hardhat task to deploy your contract
- Install
@zk-kit/incremental-merkle-treeandcircomlibjs@0.0.8to create offchain Merkle trees.
$ yarn add @zk-kit/incremental-merkle-tree circomlibjs@0.0.8 --dev
- Create a
tasksfolder and add the following file:
const { IncrementalMerkleTree } = require("@zk-kit/incremental-merkle-tree")
const { poseidon } = require("circomlibjs")
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 ContractFactory = await ethers.getContractFactory("Greeters")
const tree = new IncrementalMerkleTree(poseidon, 20, BigInt(0), 5)
for (const identityCommitment of identityCommitments) {
tree.insert(identityCommitment) // Insert the Merkle tree leaves.
}
const contract = await ContractFactory.deploy(tree.root)
await contract.deployed()
logs && console.log(`Greeters contract has been deployed to: ${contract.address}`)
return contract
})
- Import your new Hardhat task in the
hardhat.config.jsfile:
require("@nomiclabs/hardhat-waffle")
require("./tasks/deploy")
module.exports = {
solidity: "0.8.4"
}
Create your tests
-
Creating proofs requires some static files, in the future these files will be hosted on a server and made public. For now you can use the ones used in our repository for testing. Copy these files in the
staticfolder. -
Update the Hardhat test file:
const { Strategy, ZkIdentity } = require("@zk-kit/identity")
const { generateMerkleProof, Semaphore } = require("@zk-kit/protocols")
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 finalZkeyPath = "./static/semaphore_final.zkey"
it("Should greet", async () => {
const message = await signers[0].signMessage("Sign this message to create your identity!")
const identity = new ZkIdentity(Strategy.MESSAGE, message)
const identityCommitment = identity.genIdentityCommitment()
const greeting = "Hello world"
const merkleProof = generateMerkleProof(
20,
BigInt(0),
5,
identityCommitments.map(BigInt),
identityCommitment
)
const witness = Semaphore.genWitness(
identity.getTrapdoor(),
identity.getNullifier(),
merkleProof,
merkleProof.root,
greeting
)
const fullProof = await Semaphore.genProof(witness, wasmFilePath, finalZkeyPath)
const solidityProof = Semaphore.packToSolidityProof(fullProof)
const nullifierHash = Semaphore.genNullifierHash(merkleProof.root, identity.getNullifier())
const transaction = contract.greet(greeting, nullifierHash, solidityProof)
await expect(transaction).to.emit(contract, "NewGreeting").withArgs(greeting)
})
})
})
- Test your contract:
$ yarn hardhat test
Deploy your contract in a local network
You can also deploy your contract in a local Hardhat network and use it in your DApp:
$ yarn hardhat node
$ yarn hardhat deploy --network localhost