mirror of
https://github.com/AtHeartEngineering/bandada.git
synced 2026-01-09 16:58:29 -05:00
test: add tests for zk-groups contract
This commit is contained in:
@@ -6,6 +6,7 @@ import { NetworksUserConfig } from "hardhat/types"
|
||||
import { resolve } from "path"
|
||||
import { config } from "./package.json"
|
||||
import "./tasks/deploy-zk-groups"
|
||||
import "./tasks/accounts"
|
||||
|
||||
dotenvConfig({ path: resolve(__dirname, "../.env") })
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
"start": "hardhat node",
|
||||
"compile": "hardhat compile",
|
||||
"deploy": "hardhat deploy:zk-groups",
|
||||
"deploy:goerli": "hardhat deploy:zk-groups --network goerli",
|
||||
"deploy:arbitrum": "hardhat deploy:zk-groups --network arbitrum",
|
||||
"test": "hardhat test",
|
||||
"test:report-gas": "REPORT_GAS=true hardhat test",
|
||||
"test:coverage": "hardhat coverage"
|
||||
@@ -33,6 +35,7 @@
|
||||
"@openzeppelin/contracts": "^4.7.3",
|
||||
"@semaphore-protocol/contracts": "^2.5.0",
|
||||
"@semaphore-protocol/hardhat": "^0.1.0",
|
||||
"@semaphore-protocol/identity": "^2.6.1"
|
||||
"@semaphore-protocol/identity": "^2.6.1",
|
||||
"@semaphore-protocol/proof": "^2.6.1"
|
||||
}
|
||||
}
|
||||
|
||||
16
contracts/tasks/accounts.ts
Normal file
16
contracts/tasks/accounts.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Signer } from "@ethersproject/abstract-signer"
|
||||
import { task, types } from "hardhat/config"
|
||||
|
||||
task("accounts", "Prints the list of accounts")
|
||||
.addOptionalParam<boolean>("logs", "Print the logs", true, types.boolean)
|
||||
.setAction(async ({ logs }, { ethers }) => {
|
||||
const accounts: Signer[] = await ethers.getSigners()
|
||||
|
||||
if (logs) {
|
||||
for (const account of accounts) {
|
||||
console.info(await account.getAddress())
|
||||
}
|
||||
}
|
||||
|
||||
return accounts
|
||||
})
|
||||
288
contracts/test/ZKGroups.ts
Normal file
288
contracts/test/ZKGroups.ts
Normal file
@@ -0,0 +1,288 @@
|
||||
import path, { resolve } from "path"
|
||||
import { config as dotenvConfig } from "dotenv"
|
||||
import { Semaphore, ZKGroups } from "../build/typechain"
|
||||
import { utils, Signer, constants } from "ethers"
|
||||
import { run } from "hardhat"
|
||||
import { createOffchainGroupId, createIdentityCommitments } from "./utils"
|
||||
import { Group, Member } from "@semaphore-protocol/group"
|
||||
import { expect } from "chai"
|
||||
import { Identity } from "@semaphore-protocol/identity"
|
||||
import { FullProof, generateProof, packToSolidityProof, SolidityProof } from "@semaphore-protocol/proof"
|
||||
|
||||
|
||||
dotenvConfig({ path: resolve(__dirname, "../../.env")})
|
||||
|
||||
describe("ZKGroups", () => {
|
||||
let contract: ZKGroups
|
||||
let semaphore: Semaphore
|
||||
let signers: Signer[]
|
||||
let accounts: string[]
|
||||
|
||||
const wasmFilePath = `../snark-artifacts/semaphore.wasm`
|
||||
const zkeyFilePath = `../snark-artifacts/semaphore.zkey`
|
||||
|
||||
const treeDepth = 20
|
||||
const offchainGroupName = utils.formatBytes32String("TestGroupName")
|
||||
const offchainGroupId = createOffchainGroupId(offchainGroupName)
|
||||
const group = new Group(treeDepth)
|
||||
const members = createIdentityCommitments(3)
|
||||
|
||||
group.addMembers(members)
|
||||
|
||||
before(async () => {
|
||||
const { address: verifierAddress } = await run("deploy:verifier", { logs: false, merkleTreeDepth: treeDepth})
|
||||
semaphore = await run("deploy:semaphore", {
|
||||
logs: false,
|
||||
verifiers: [{ merkleTreeDepth: treeDepth, contractAddress: verifierAddress }]
|
||||
})
|
||||
|
||||
contract = await run("deploy:zk-groups", {
|
||||
logs: false,
|
||||
verifiers: [{ merkleTreeDepth: treeDepth, contractAddress: verifierAddress}],
|
||||
semaphore: semaphore.address
|
||||
})
|
||||
|
||||
signers = await run("accounts", { logs: false })
|
||||
accounts = await Promise.all(signers.map((signer: Signer) => signer.getAddress()))
|
||||
})
|
||||
|
||||
describe("Off-chain groups", () => {
|
||||
describe("# updateOffchainGroup", () => {
|
||||
it("Should not publish zk-groups off-chain groups if there is an unsupported merkle tree depth", async () => {
|
||||
const transaction = contract.updateOffchainGroups(
|
||||
[{name: offchainGroupName, merkleTreeRoot: 123, merkleTreeDepth: 10}]
|
||||
)
|
||||
|
||||
await expect(transaction).to.be.revertedWith("MerkleTreeDepth is not supported")
|
||||
})
|
||||
|
||||
it("Should publish zk-groups off-chain groups", async () => {
|
||||
const groups: {name: string, merkleTreeRoot: Member, merkleTreeDepth: number }[] = [
|
||||
{name: offchainGroupName, merkleTreeRoot: group.root, merkleTreeDepth: group.depth}
|
||||
]
|
||||
|
||||
const transaction = contract.updateOffchainGroups(groups)
|
||||
|
||||
await expect(transaction).to
|
||||
.emit(contract, "OffchainGroupUpdated")
|
||||
.withArgs(offchainGroupId, groups[0].name, groups[0].merkleTreeRoot, groups[0].merkleTreeDepth)
|
||||
})
|
||||
})
|
||||
|
||||
describe("# getOffchainRoot", () => {
|
||||
it("Should get the merkle tree root of an zk-groups off-chain group", async () => {
|
||||
const merkleTreeRoot = await contract.getOffchainRoot(offchainGroupId)
|
||||
|
||||
expect(merkleTreeRoot).to.equal("10984560832658664796615188769057321951156990771630419931317114687214058410144")
|
||||
})
|
||||
})
|
||||
|
||||
describe("# getOffchainDepth", () => {
|
||||
it("Should get the merkle tree depth of an zk-groups off-chain group", async () => {
|
||||
const merkleTreeDepth = await contract.getOffchainDepth(offchainGroupId)
|
||||
|
||||
expect(merkleTreeDepth).to.equal(treeDepth)
|
||||
})
|
||||
})
|
||||
|
||||
describe("# verifyOffchainGroupProof", () => {
|
||||
const signal = utils.formatBytes32String("Hello zk-world")
|
||||
const identity = new Identity("0")
|
||||
|
||||
let fullProof: FullProof
|
||||
let solidityProof: SolidityProof
|
||||
|
||||
before(async () => {
|
||||
fullProof = await generateProof(identity, group, group.root, signal, { wasmFilePath , zkeyFilePath })
|
||||
solidityProof = packToSolidityProof(fullProof.proof)
|
||||
})
|
||||
|
||||
it("Should not verify a proof if the group does not exist", async () => {
|
||||
const transaction = contract.verifyOffchainGroupProof(1234, signal, 0, 0, [0,0,0,0,0,0,0,0])
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Offchain group does not exist")
|
||||
})
|
||||
|
||||
it("Should throw an exception if the proof is not valid", async () => {
|
||||
const transaction = contract.verifyOffchainGroupProof(
|
||||
offchainGroupId,
|
||||
signal,
|
||||
fullProof.publicSignals.nullifierHash,
|
||||
0,
|
||||
solidityProof
|
||||
)
|
||||
|
||||
await expect(transaction).to.be.reverted
|
||||
})
|
||||
|
||||
it("Should verify a proof for an off-chain group correctly", async () => {
|
||||
const transaction = contract.verifyOffchainGroupProof(
|
||||
offchainGroupId,
|
||||
signal,
|
||||
fullProof.publicSignals.nullifierHash,
|
||||
fullProof.publicSignals.merkleRoot,
|
||||
solidityProof
|
||||
)
|
||||
|
||||
await expect(transaction).to.emit(contract, "ProofVerified").withArgs(
|
||||
offchainGroupId,
|
||||
fullProof.publicSignals.nullifierHash,
|
||||
fullProof.publicSignals.externalNullifier,
|
||||
signal
|
||||
)
|
||||
})
|
||||
|
||||
it("Should not verify the same proof for an off-chain group twice", async () => {
|
||||
const transaction = contract.verifyOffchainGroupProof(
|
||||
offchainGroupId,
|
||||
signal,
|
||||
fullProof.publicSignals.nullifierHash,
|
||||
fullProof.publicSignals.merkleRoot,
|
||||
solidityProof
|
||||
)
|
||||
|
||||
await expect(transaction).to.be.revertedWith("you cannot use the same nullifier twice")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("On-chain groups", () => {
|
||||
const groupId = 1
|
||||
describe("# createOnchainGroup", () => {
|
||||
it("Should create a on-chain group if you pass the deployed semaphore contract", async () => {
|
||||
const transaction = contract.connect(signers[0]).createOnchainGroup(groupId, treeDepth, 0, accounts[0], 20) //20 seconds
|
||||
|
||||
await expect(transaction).to.emit(semaphore, "GroupCreated").withArgs(groupId, treeDepth, 0)
|
||||
await expect(transaction).to.emit(semaphore, "GroupAdminUpdated").withArgs(groupId, constants.AddressZero, contract.address)
|
||||
await expect(transaction).to.emit(contract, "OnchainGroupAdminUpdated").withArgs(groupId,constants.AddressZero, accounts[0])
|
||||
})
|
||||
})
|
||||
|
||||
describe("# updateOnchainGroupAdmin", () => {
|
||||
it("Should not update a on-chain group admin if the caller is not the group admin", async () => {
|
||||
const transaction = contract.connect(signers[1]).updateOnchainGroupAdmin(groupId, accounts[1])
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Caller is not the onchain group admin")
|
||||
})
|
||||
|
||||
it("Should update a on-chain group admin in zk-groups contract", async () => {
|
||||
const transaction = contract.connect(signers[0]).updateOnchainGroupAdmin(groupId, accounts[1])
|
||||
|
||||
await expect(transaction).to.emit(contract, "OnchainGroupAdminUpdated").withArgs(groupId, accounts[0], accounts[1])
|
||||
})
|
||||
})
|
||||
|
||||
describe("# addMember", () => {
|
||||
it("Should not add a member if the caller is not the on-chain group admin", async () => {
|
||||
const transaction = contract.connect(signers[2]).addMember(groupId, members[0])
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Caller is not the onchain group admin")
|
||||
})
|
||||
|
||||
it("Should add a new member in an existing on-chain group if you pass the deployed semaphore contract", async () => {
|
||||
const transaction = contract.connect(signers[1]).addMember(groupId, members[0])
|
||||
|
||||
await expect(transaction).to.emit(semaphore, "MemberAdded").withArgs(
|
||||
groupId,
|
||||
0,
|
||||
members[0],
|
||||
"18951329906296061785889394467312334959162736293275411745101070722914184798221"
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("# addMembers", () => {
|
||||
it("Should add new members to an existing on-chain group if you pass the deployed semaphore contract", async () => {
|
||||
const groupId = 2
|
||||
|
||||
await contract.createOnchainGroup(groupId, treeDepth, 0, accounts[0], 20)
|
||||
|
||||
const transaction = contract.connect(signers[0]).addMembers(groupId, members)
|
||||
|
||||
await expect(transaction).to.emit(semaphore, "MemberAdded").withArgs(
|
||||
groupId,
|
||||
2,
|
||||
members[2],
|
||||
group.root
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("# updateMember", () => {
|
||||
it("Should not update a member if the caller is not the on-chain group admin", async () => {
|
||||
const transaction = contract.updateMember(groupId, members[0], members[1], [0,1], [0,1])
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Caller is not the onchain group admin")
|
||||
})
|
||||
|
||||
it("Should update a member from an existing on-chain group if you pass the deployed semaphore contract", async () => {
|
||||
const groupId = 2
|
||||
const newMember = BigInt(123)
|
||||
|
||||
group.updateMember(0, newMember)
|
||||
|
||||
const { siblings, pathIndices, root } = group.generateProofOfMembership(0)
|
||||
|
||||
const transaction = contract.connect(signers[0]).updateMember(groupId, members[0], newMember, siblings, pathIndices)
|
||||
|
||||
await expect(transaction).to.emit(semaphore, "MemberUpdated").withArgs(groupId, 0, members[0], newMember, root)
|
||||
})
|
||||
})
|
||||
|
||||
describe("# removeMember", () => {
|
||||
it("Should not remove a member if the caller is not the on-chain group admin", async () => {
|
||||
const transaction = contract.removeMember(groupId, members[0], [0,1], [0,1])
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Caller is not the onchain group admin")
|
||||
})
|
||||
|
||||
it("Should remove a member from an existing on-chain group if you pass the deployed semaphore contract", async () => {
|
||||
const groupId = 2
|
||||
const member = BigInt(123)
|
||||
|
||||
group.removeMember(0)
|
||||
|
||||
const { siblings, pathIndices, root } = group.generateProofOfMembership(0)
|
||||
|
||||
const transaction = contract.connect(signers[0]).removeMember(groupId, member, siblings, pathIndices)
|
||||
|
||||
await expect(transaction).to.emit(semaphore, "MemberRemoved").withArgs(groupId, 0, member, root)
|
||||
})
|
||||
})
|
||||
|
||||
describe("# verifyOnchainGroupProof", () => {
|
||||
const signal = utils.formatBytes32String("Hello zk-world")
|
||||
const identity = new Identity("1")
|
||||
|
||||
let fullProof: FullProof
|
||||
let solidityProof: SolidityProof
|
||||
|
||||
it("Should verify a proof for an on-chain group correctly if you pass the deployed semaphore contract", async () => {
|
||||
const groupId = 2
|
||||
|
||||
fullProof = await generateProof(identity, group, group.root, signal, {
|
||||
wasmFilePath,
|
||||
zkeyFilePath
|
||||
})
|
||||
solidityProof = packToSolidityProof(fullProof.proof)
|
||||
|
||||
const transaction = contract.verifyOnchainGroupProof(
|
||||
groupId,
|
||||
group.root,
|
||||
signal,
|
||||
fullProof.publicSignals.nullifierHash,
|
||||
fullProof.publicSignals.merkleRoot,
|
||||
solidityProof
|
||||
)
|
||||
|
||||
await expect(transaction).to.emit(semaphore, "ProofVerified").withArgs(
|
||||
groupId,
|
||||
group.root,
|
||||
fullProof.publicSignals.nullifierHash,
|
||||
fullProof.publicSignals.externalNullifier,
|
||||
signal
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
23
contracts/test/utils.ts
Normal file
23
contracts/test/utils.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Identity } from "@semaphore-protocol/identity"
|
||||
import { utils } from "ethers"
|
||||
|
||||
export const SNARK_SCALAR_FIELD = BigInt(
|
||||
"21888242871839275222246405745257275088548364400416034343698204186575808495617"
|
||||
)
|
||||
|
||||
export function createOffchainGroupId(name: string): bigint {
|
||||
return BigInt(utils.solidityKeccak256(["string","bytes32"],["Offchain_",name])) % SNARK_SCALAR_FIELD
|
||||
}
|
||||
|
||||
export function createIdentityCommitments(n: number): bigint[] {
|
||||
const identityCommitments: bigint[] = []
|
||||
|
||||
for (let i = 0; i < n; i++) {
|
||||
const identity = new Identity(i.toString())
|
||||
const identityCommitment = identity.getCommitment()
|
||||
|
||||
identityCommitments.push(identityCommitment)
|
||||
}
|
||||
|
||||
return identityCommitments
|
||||
}
|
||||
@@ -1,14 +1,11 @@
|
||||
{
|
||||
//"extends": "../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"target": "es2020",
|
||||
"module": "commonjs",
|
||||
//"outDir": "dist",
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
//"include": ["scripts/**/*", "tasks/**/*", "test/**/*", "build/typechain/**/*", "types/**/*"],
|
||||
}
|
||||
}
|
||||
|
||||
BIN
snark-artifacts/semaphore.wasm
Normal file
BIN
snark-artifacts/semaphore.wasm
Normal file
Binary file not shown.
BIN
snark-artifacts/semaphore.zkey
Normal file
BIN
snark-artifacts/semaphore.zkey
Normal file
Binary file not shown.
Reference in New Issue
Block a user