Skip to main content

Semaphore proofs

If you've read the guide on how to use the Interep groups you may now be wondering how you can allow users to create Semaphore proofs, and how to verify that these proofs are valid.

In this section, we will learn how to get all the parameters for creating valid Semaphore proofs and how to use the Interep contract to correctly verify them.


Before going any further, if you are not familiar with Semaphore, read the official documentation.

Semaphore identity

Since only a user belonging to an Interep group can create a valid proof, it is necessary for users to re-generate their Semaphore identity with @interep/identity.

  1. Get the Ethereum account signer from Metamask:
import detectEthereumProvider from "@metamask/detect-provider"
import { ethers } from "ethers"

const ethereumProvider = await detectEthereumProvider()
const provider = new ethers.providers.Web3Provider(ethereumProvider)
const signer = provider.getSigner()
  1. Create the Semaphore identity:
import createIdentity from "@interep/identity"

const sign = (message) => signer.signMessage(message)

const identity = await createIdentity(sign, "Github")

While the identity commitment can be public, the Semaphore identity must remain private, as it contains the parameters necessary to create Semaphore proofs.

Semaphore proof

Once users have generated their Semaphore identities, they can be used to create Semaphore proofs with @interep/proof.

Creating Semaphore proofs also requires some zero-knowledge static files. In the future these files will be hosted on a server and made public, but for now you can use the ones used in the Semaphore repository for testing.

import createProof from "@interep/proof"

const groupProvider = "github"
const groupName = "gold"
const externalNullifier = 1
const signal = "Hello World"

const snarkArtifacts = {
wasmFilePath: "./semaphore.wasm",
zkeyFilePath: "./semaphore.zkey"

const { publicSignals, solidityProof } = await createProof(identity, groupProvider, groupName, externalNullifier, signal, snarkArtifacts)

Onchain verification

Finally, you can verify Semaphore proofs and use the anonymous user signals in your app.

  1. Check the Interep supported networks and contract addresses in the repository and create your contract:
import "./IInterep.sol";

contract MyContract {

IInterep interep;

constructor(address interepAddress) {
interep = IInterep(interepAddress);

function myFunction(
uint256 groupId,
bytes32 calldata signal,
uint256 nullifierHash,
uint256 externalNullifier,
uint256[8] calldata proof
) public {
interep.verifyProof(groupId, signal, nullifierHash, externalNullifier, proof);

// Use the anonymous user signal here...
  1. Call your contract function passing Semaphore proofs:
import Interep from "./Interep.json" // Interep contract interface.
import { Contract, providers, Wallet } from "ethers"

const provider = new providers.JsonRpcProvider("<infura-api-key>")
const adminWallet = Wallet.fromMnemonic("<admin-mnemonic>").connect(provider)
const contract = new Contract("<interep-contract-address>", Interep.abi, adminWallet)

await contract.myFunction(groupId, signal, publicSignals.nullifierHash, publicSignals.externalNullifier, solidityProof)

Check the available Interep contract addresses and generate the Interep contract interface (ABI) running yarn compile in the repository.