owned this note
owned this note
Published
Linked with GitHub
# Semaphore Noir Tutorial
# Installation
To install semaphore-noir, we rely on local dependencies currently. It shall be moved to the npm registry in future updates.
1. Clone the repositories and switch to correct branch
```bash
git clone https://github.com/hashcloak/semaphore-noir.git
git clone https://github.com/hashcloak/snark-artifacts.git
cd semaphore-noir && git fetch && git switch noir-support
cd snark-artifacts && git fetch && git switch semaphore-noir
```
2. In [proof/package.json](https://github.com/hashcloak/semaphore-noir/blob/noir-support/packages/proof/package.json#L58) and [noir-proof-batch/package.json](), update the dependencies for `@zk-kit/artifacts` to the local path of `snark-artifacts` cloned in step 1.
3. Build snark-artifact
```bash
cd snark-artifact/packages/artifacts
pnpm install
pnpm build
```
4. Build and test semaphore-noir
```bash
cd semaphore-noir
yarn install
yarn build
yarn test
```
# Semaphore Noir identities / groups
Semaphore identities and groups in Semaphore Noir are identical with the Semaphore V4. Please refer to the guides for [identities](https://docs.semaphore.pse.dev/guides/identities) and [groups](https://docs.semaphore.pse.dev/guides/groups).
Note: for installation, run
```bash
yarn add "@semaphore-protocol/proof@file:<YOUR_PATH_TO>/hashcloak-noir/semaphore-noir/packages/identity"
yarn add "@semaphore-protocol/proof@file:<YOUR_PATH_TO>/hashcloak-noir/semaphore-noir/packages/group"
```
# Semaphore Noir proof
## Install identity, group and proof
```bash
yarn add "@semaphore-protocol/proof@file:<YOUR_PATH_TO>/hashcloak-noir/semaphore-noir/packages/identity"
yarn add "@semaphore-protocol/proof@file:<YOUR_PATH_TO>/hashcloak-noir/semaphore-noir/packages/group"
yarn add "@semaphore-protocol/proof@file:<YOUR_PATH_TO>/hashcloak-noir/semaphore-noir/packages/proof"
```
0. Import necessary packages
```ts=
import { Group } from "@semaphore-protocol/group"
import { Identity } from "@semaphore-protocol/identity"
import {
generateNoirProof,
verifyNoirProof,
SemaphoreNoirProof,
SemaphoreNoirBackend,
initSemaphoreNoirBackend,
getMerkleTreeDepth
} from "@semaphore-protocol/proof"
```
1. Create identities & groups
For details on creating identities and groups, refer to the guides above. In this example, we will simply create an identity and a group as:
```ts=
const identity = new Identity("secret")
const group = new Group([identity.commitment, 1n, 2n, 3n, 4n, 5n, 6n])
```
2. Choose the scope and message
Each proof requires a scope, on which each user may only generate one valid proof. The scope, together with the user's private key, is used to generate the nullifier, which is the value you can actually use to check whether a proof with that scope has already been generated by that user. In a voting application where double-voting must be prevented, the scope could be the ballot id, or the Merkle root of the group.
```ts=
const message = "Hello world"
const scope = "Scope"
```
3. Initialize a Noir proving backend
To improve efficiency, we separate the initialization of a proving backend with the actual proving step. That way, we can reuse the proving backend as long as the `merkle_depth` in the backend can generate a Merkle tree big enough to hold all the members of the group we are proving / verifying.
A Semaphore group is stored in a Merkle tree. The depth of a tree decides how many members a group can hold ($2^{merkle\_depth}$). The smaller the depth is, the more efficient the ZK circuit become.
```ts=
const merkleTreeDepth = 4
// a helper function to calculate the tree depth
// const merkleTreeDepth = getMerkleTreeDepth(identity, group)
const backend = await initSemaphoreNoirBackend(merkleTreeDepth)
```
4. Generate and Verify a proof
```ts=
const proof = await generateNoirProof(identity, group, message, scope, backend)
const isValid = await verifyNoirProof(proof, backend)
console.log(isValid) // true
```
# Semaphore Noir Contracts
We updated the Semaphore Solidity contracts that it is now using a [UltraHonk verifier](https://github.com/hashcloak/semaphore-noir/blob/noir-support/packages/contracts-noir/contracts/base/SemaphoreNoirVerifier.sol). Other functionalities should be similar to the Semaphore V4.
## Deploy the Contracts
To interact with the contract, first deploy the contracts with the [deploy script](https://github.com/hashcloak/semaphore-noir/blob/noir-support/packages/contracts-noir/tasks/deploy.ts). (Deployed contracts on testnet can be found [here](https://github.com/hashcloak/semaphore-noir/blob/noir-support-part2/packages/utils/src/networks/deployed-contracts-noir.json))
1. Setup [hardhat.config.ts](https://github.com/hashcloak/semaphore-noir/blob/noir-support/packages/contracts-noir/hardhat.config.ts) with intended networks.
```ts
// e.g. local testnet
networks: {
test: {
url: "http://127.0.0.1:8545/",
accounts: ["private_key"]
},
...
},
```
3. Run the deploy script
```
yarn compile
yarn deploy --network <network_name>
```
## Interact with the Contracts
0. Setup ether.js
```ts=
// import group, proof, identity as above
import { ethers } from "ethers";
// we are using local testnet in this example
const provider = new ethers.JsonRpcProvider("http://127.0.0.1:8545/")
const signer = await provider.getSigner()
const semaphoreContract = new ethers.Contract("SemaphoreNoir_addr", SemaphoreNoir_abi, signer)
```
1. Create group on-chain and off-chain
```ts=
// create a Semaphore identity
const identity = new Identity("0")
// members of the group
const members = Array.from({ length: 3 }, (_, i) => new Identity(i.toString())).map(
({ commitment }) => commitment
)
// create a group with the members locally
const group = new Group(members)
// create a group and add 3 members on chain
let tx = await semaphoreContract["createGroup(address)"](signer)
await tx.wait()
// groupId = 1 since it is the first group we create
const groupId = 1
// add members to group
tx = await semaphoreContract.addMembers(groupId, members)
await tx.wait()
```
2. Update and Remove Members
```ts=
// Remove the third member.
{
// off-chain
group.removeMember(2)
const { siblings } = group.generateMerkleProof(2)
//on-chain
let tx = await semaphoreContract.removeMember(groupId, members[2], siblings)
await tx.wait()
}
// Update the second member.
{
// off-chain
group.updateMember(1, members[2])
const { siblings } = group.generateMerkleProof(1)
// on-chain
let tx = await semaphoreContract.updateMember(groupId, members[1], members[2], siblings)
await tx.wait()
}
```
3. Create and Verify Proofs
We can directly use the `proof` package mentioned above to create a proof to verify on-chain. However, the on-chain verifier uses a different hash function (keccak) than the off-chain verifier (poseidon). Thus we have to add an additional flag to indicate the use of keccak when generating a proof for the on-chain verifier.
```ts=
const treeDepth = getMerkleTreeDepth(identity, group)
const backend = await initSemaphoreNoirBackend(treeDepth)
// generate a proof with keccak = true
const proof = await generateNoirProof(identity, group, "msg", group.root, backend, true)
tx = await
semaphoreContract.validateProof(groupId, proof)
// tx will emit a "ProofValidated" event if success
const receipt = await tx.wait()
console.log(receipt.logs)
```