mirror of
https://github.com/semaphore-protocol/semaphore.git
synced 2026-01-13 00:28:00 -05:00
Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
12fd0f7a80 | ||
|
|
78da99055d | ||
|
|
77e4770b53 | ||
|
|
799afc82f4 | ||
|
|
46fe2fc8f8 | ||
|
|
7bb9554388 | ||
|
|
d88a71ec4d | ||
|
|
2afc4ce1de | ||
|
|
64f7b24c53 | ||
|
|
88ba0af2d2 | ||
|
|
c2e8ba6856 | ||
|
|
f9a8d68641 | ||
|
|
df84100c22 | ||
|
|
b962339203 | ||
|
|
073f5a5772 | ||
|
|
b26f74a453 | ||
|
|
e9cac671f2 | ||
|
|
ecf8dcafb1 | ||
|
|
f90c99193a | ||
|
|
a826708320 | ||
|
|
43370202a7 | ||
|
|
a4d1180d26 | ||
|
|
5c42f9e09c | ||
|
|
c8362e373b | ||
|
|
06765f2f88 | ||
|
|
f26c84445e | ||
|
|
61a2d6adc2 | ||
|
|
eddd6b3dd5 | ||
|
|
fa561f8f00 | ||
|
|
c1842eefc3 | ||
|
|
320187ff89 | ||
|
|
abbf1a1d30 | ||
|
|
59269b067d | ||
|
|
21bd9fe540 | ||
|
|
f60a4c02f1 | ||
|
|
d012310ae1 | ||
|
|
f01cb91472 | ||
|
|
31e6954aff | ||
|
|
7518d938d1 | ||
|
|
083d8a03a4 | ||
|
|
16c9e90ae4 | ||
|
|
1fe2745594 | ||
|
|
18d4b740ca | ||
|
|
8392173370 | ||
|
|
26490304e4 | ||
|
|
1878ce2320 | ||
|
|
98aed04bd9 | ||
|
|
5a0585cec2 | ||
|
|
1a51263e5c |
14
.github/workflows/auto-assign.yml
vendored
Normal file
14
.github/workflows/auto-assign.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
name: auto-assign
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
run:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: wow-actions/auto-assign@v3
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
reviewers: org/core-devs
|
||||
4
.husky/commit-msg
Executable file
4
.husky/commit-msg
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npx --no-install commitlint --edit $1
|
||||
22
README.md
22
README.md
@@ -217,6 +217,28 @@ The core of the Semaphore protocol is in the [circuit logic](/packages/circuits/
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="/packages/heyauthn">
|
||||
@semaphore-protocol/heyauthn
|
||||
</a>
|
||||
<a href="https://semaphore-protocol.github.io/semaphore/heyauthn">
|
||||
(docs)
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<!-- NPM version -->
|
||||
<a href="https://npmjs.org/package/@semaphore-protocol/heyauthn">
|
||||
<img src="https://img.shields.io/npm/v/@semaphore-protocol/heyauthn.svg?style=flat-square" alt="NPM version" />
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<!-- Downloads -->
|
||||
<a href="https://npmjs.org/package/@semaphore-protocol/heyauthn">
|
||||
<img src="https://img.shields.io/npm/dm/@semaphore-protocol/heyauthn.svg?style=flat-square" alt="Downloads" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tbody>
|
||||
|
||||
</table>
|
||||
|
||||
7
changelogithub.config.json
Normal file
7
changelogithub.config.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"types": {
|
||||
"feat": { "title": "🚀 Features" },
|
||||
"fix": { "title": "🐞 Bug Fixes" },
|
||||
"refactor": { "title": "♻️ Refactoring" }
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@
|
||||
"version:release": "changelogithub",
|
||||
"commit": "cz",
|
||||
"precommit": "lint-staged",
|
||||
"postinstall": "yarn download:snark-artifacts"
|
||||
"postinstall": "yarn download:snark-artifacts && husky install"
|
||||
},
|
||||
"keywords": [
|
||||
"ethereum",
|
||||
@@ -67,6 +67,7 @@
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-import": "^2.25.2",
|
||||
"eslint-plugin-jest": "^25.7.0",
|
||||
"husky": "^8.0.3",
|
||||
"jest": "^27.4.1",
|
||||
"jest-config": "^27.4.7",
|
||||
"lint-staged": "^12.1.7",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
ETHEREUM_URL=
|
||||
INFURA_API_KEY=
|
||||
ETHEREUM_PRIVATE_KEY=
|
||||
REPORT_GAS=false
|
||||
COINMARKETCAP_API_KEY=
|
||||
|
||||
30
packages/cli-template-hardhat/contracts/Feedback.sol
Normal file
30
packages/cli-template-hardhat/contracts/Feedback.sol
Normal file
@@ -0,0 +1,30 @@
|
||||
//SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.4;
|
||||
|
||||
import "@semaphore-protocol/contracts/interfaces/ISemaphore.sol";
|
||||
|
||||
contract Feedback {
|
||||
ISemaphore public semaphore;
|
||||
|
||||
uint256 public groupId;
|
||||
|
||||
constructor(address semaphoreAddress, uint256 _groupId) {
|
||||
semaphore = ISemaphore(semaphoreAddress);
|
||||
groupId = _groupId;
|
||||
|
||||
semaphore.createGroup(groupId, 20, address(this));
|
||||
}
|
||||
|
||||
function joinGroup(uint256 identityCommitment) external {
|
||||
semaphore.addMember(groupId, identityCommitment);
|
||||
}
|
||||
|
||||
function sendFeedback(
|
||||
uint256 feedback,
|
||||
uint256 merkleTreeRoot,
|
||||
uint256 nullifierHash,
|
||||
uint256[8] calldata proof
|
||||
) external {
|
||||
semaphore.verifyProof(groupId, merkleTreeRoot, feedback, nullifierHash, groupId, proof);
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
//SPDX-License-Identifier: Unlicense
|
||||
pragma solidity ^0.8.4;
|
||||
|
||||
import "@semaphore-protocol/contracts/interfaces/ISemaphore.sol";
|
||||
|
||||
/// @title Greeter contract.
|
||||
/// @dev The following code is just a example to show how Semaphore can be used.
|
||||
contract Greeter {
|
||||
event NewGreeting(bytes32 greeting);
|
||||
event NewUser(uint256 identityCommitment, bytes32 username);
|
||||
|
||||
ISemaphore public semaphore;
|
||||
|
||||
uint256 groupId;
|
||||
mapping(uint256 => bytes32) users;
|
||||
|
||||
constructor(address semaphoreAddress, uint256 _groupId) {
|
||||
semaphore = ISemaphore(semaphoreAddress);
|
||||
groupId = _groupId;
|
||||
|
||||
semaphore.createGroup(groupId, 20, address(this));
|
||||
}
|
||||
|
||||
function joinGroup(uint256 identityCommitment, bytes32 username) external {
|
||||
semaphore.addMember(groupId, identityCommitment);
|
||||
|
||||
users[identityCommitment] = username;
|
||||
|
||||
emit NewUser(identityCommitment, username);
|
||||
}
|
||||
|
||||
function greet(
|
||||
bytes32 greeting,
|
||||
uint256 merkleTreeRoot,
|
||||
uint256 nullifierHash,
|
||||
uint256[8] calldata proof
|
||||
) external {
|
||||
semaphore.verifyProof(groupId, merkleTreeRoot, uint256(greeting), nullifierHash, groupId, proof);
|
||||
|
||||
emit NewGreeting(greeting);
|
||||
}
|
||||
}
|
||||
@@ -1,50 +1,63 @@
|
||||
import "@nomicfoundation/hardhat-toolbox"
|
||||
import "@nomiclabs/hardhat-ethers"
|
||||
import "@nomicfoundation/hardhat-chai-matchers"
|
||||
import "@semaphore-protocol/hardhat"
|
||||
import "@typechain/hardhat"
|
||||
import { config as dotenvConfig } from "dotenv"
|
||||
import "hardhat-gas-reporter"
|
||||
import { HardhatUserConfig } from "hardhat/config"
|
||||
import { NetworksUserConfig } from "hardhat/types"
|
||||
import { resolve } from "path"
|
||||
import "solidity-coverage"
|
||||
import { config } from "./package.json"
|
||||
import "./tasks/deploy"
|
||||
|
||||
dotenvConfig()
|
||||
dotenvConfig({ path: resolve(__dirname, "../../.env") })
|
||||
|
||||
function getNetworks(): NetworksUserConfig {
|
||||
if (process.env.ETHEREUM_URL && process.env.ETHEREUM_PRIVATE_KEY) {
|
||||
const accounts = [`0x${process.env.ETHEREUM_PRIVATE_KEY}`]
|
||||
|
||||
return {
|
||||
goerli: {
|
||||
url: process.env.ETHEREUM_URL,
|
||||
chainId: 5,
|
||||
accounts
|
||||
},
|
||||
sepolia: {
|
||||
url: process.env.ETHEREUM_URL,
|
||||
chainId: 11155111,
|
||||
accounts
|
||||
},
|
||||
mumbai: {
|
||||
url: process.env.ETHEREUM_URL,
|
||||
chainId: 80001,
|
||||
accounts
|
||||
},
|
||||
"optimism-goerli": {
|
||||
url: process.env.ETHEREUM_URL,
|
||||
chainId: 420,
|
||||
accounts
|
||||
},
|
||||
arbitrum: {
|
||||
url: process.env.ETHEREUM_URL,
|
||||
chainId: 42161,
|
||||
accounts
|
||||
}
|
||||
}
|
||||
if (!process.env.INFURA_API_KEY || !process.env.ETHEREUM_PRIVATE_KEY) {
|
||||
return {}
|
||||
}
|
||||
|
||||
return {}
|
||||
const accounts = [`0x${process.env.ETHEREUM_PRIVATE_KEY}`]
|
||||
const infuraApiKey = process.env.INFURA_API_KEY
|
||||
|
||||
return {
|
||||
goerli: {
|
||||
url: `https://goerli.infura.io/v3/${infuraApiKey}`,
|
||||
chainId: 5,
|
||||
accounts
|
||||
},
|
||||
sepolia: {
|
||||
url: `https://sepolia.infura.io/v3/${infuraApiKey}`,
|
||||
chainId: 11155111,
|
||||
accounts
|
||||
},
|
||||
mumbai: {
|
||||
url: `https://polygon-mumbai.infura.io/v3/${infuraApiKey}`,
|
||||
chainId: 80001,
|
||||
accounts
|
||||
},
|
||||
"optimism-goerli": {
|
||||
url: `https://optimism-goerli.infura.io/v3/${infuraApiKey}`,
|
||||
chainId: 420,
|
||||
accounts
|
||||
},
|
||||
arbitrum: {
|
||||
url: `https://arbitrum-mainnet.infura.io/v3/${infuraApiKey}`,
|
||||
chainId: 42161,
|
||||
accounts
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const config: HardhatUserConfig = {
|
||||
solidity: "0.8.4",
|
||||
const hardhatConfig: HardhatUserConfig = {
|
||||
solidity: config.solidity,
|
||||
paths: {
|
||||
sources: config.paths.contracts,
|
||||
tests: config.paths.tests,
|
||||
cache: config.paths.cache,
|
||||
artifacts: config.paths.build.contracts
|
||||
},
|
||||
networks: {
|
||||
hardhat: {
|
||||
chainId: 1337
|
||||
@@ -55,7 +68,11 @@ const config: HardhatUserConfig = {
|
||||
currency: "USD",
|
||||
enabled: process.env.REPORT_GAS === "true",
|
||||
coinmarketcap: process.env.COINMARKETCAP_API_KEY
|
||||
},
|
||||
typechain: {
|
||||
outDir: config.paths.build.typechain,
|
||||
target: "ethers-v5"
|
||||
}
|
||||
}
|
||||
|
||||
export default config
|
||||
export default hardhatConfig
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
{
|
||||
"name": "@semaphore-protocol/cli-template-hardhat",
|
||||
"version": "3.2.0",
|
||||
"version": "3.4.0",
|
||||
"description": "Semaphore Hardhat template.",
|
||||
"license": "Unlicense",
|
||||
"files": [
|
||||
".gitignore",
|
||||
".env.example",
|
||||
"contracts/",
|
||||
"scripts/",
|
||||
"tasks/",
|
||||
"test/",
|
||||
"hardhat.config.ts",
|
||||
@@ -18,12 +19,14 @@
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "hardhat node",
|
||||
"dev": "hardhat node & yarn compile && yarn deploy --network localhost",
|
||||
"compile": "hardhat compile",
|
||||
"deploy": "hardhat deploy",
|
||||
"test": "hardhat test",
|
||||
"download:snark-artifacts": "hardhat run scripts/download-snark-artifacts.ts",
|
||||
"deploy": "yarn compile && hardhat deploy",
|
||||
"test": "hardhat run scripts/download-snark-artifacts.ts && hardhat test",
|
||||
"test:report-gas": "REPORT_GAS=true hardhat test",
|
||||
"test:coverage": "hardhat coverage"
|
||||
"test:coverage": "hardhat coverage",
|
||||
"typechain": "hardhat typechain"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ethersproject/abi": "^5.4.7",
|
||||
@@ -33,10 +36,10 @@
|
||||
"@nomicfoundation/hardhat-toolbox": "^2.0.0",
|
||||
"@nomiclabs/hardhat-ethers": "^2.0.0",
|
||||
"@nomiclabs/hardhat-etherscan": "^3.0.0",
|
||||
"@semaphore-protocol/group": "3.2.0",
|
||||
"@semaphore-protocol/hardhat": "3.2.0",
|
||||
"@semaphore-protocol/identity": "3.2.0",
|
||||
"@semaphore-protocol/proof": "3.2.0",
|
||||
"@semaphore-protocol/group": "3.4.0",
|
||||
"@semaphore-protocol/hardhat": "3.4.0",
|
||||
"@semaphore-protocol/identity": "3.4.0",
|
||||
"@semaphore-protocol/proof": "3.4.0",
|
||||
"@typechain/ethers-v5": "^10.1.0",
|
||||
"@typechain/hardhat": "^6.1.2",
|
||||
"@types/chai": "^4.2.0",
|
||||
@@ -55,6 +58,21 @@
|
||||
"typescript": ">=4.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@semaphore-protocol/contracts": "3.2.0"
|
||||
"@semaphore-protocol/contracts": "3.4.0"
|
||||
},
|
||||
"config": {
|
||||
"solidity": {
|
||||
"version": "0.8.4"
|
||||
},
|
||||
"paths": {
|
||||
"contracts": "./contracts",
|
||||
"tests": "./test",
|
||||
"cache": "./cache",
|
||||
"build": {
|
||||
"snark-artifacts": "./build/snark-artifacts",
|
||||
"contracts": "./build/contracts",
|
||||
"typechain": "./build/typechain"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import download from "download"
|
||||
import fs from "fs"
|
||||
import { config } from "../package.json"
|
||||
|
||||
async function main() {
|
||||
const snarkArtifactsPath = config.paths.build["snark-artifacts"]
|
||||
const url = `http://www.trusted-setup-pse.org/semaphore/${20}`
|
||||
|
||||
if (!fs.existsSync(snarkArtifactsPath)) {
|
||||
fs.mkdirSync(snarkArtifactsPath, { recursive: true })
|
||||
}
|
||||
|
||||
if (!fs.existsSync(`${snarkArtifactsPath}/semaphore.zkey`)) {
|
||||
await download(`${url}/semaphore.wasm`, snarkArtifactsPath)
|
||||
await download(`${url}/semaphore.zkey`, snarkArtifactsPath)
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
.then(() => process.exit(0))
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
process.exit(1)
|
||||
})
|
||||
@@ -1,6 +1,6 @@
|
||||
import { task, types } from "hardhat/config"
|
||||
|
||||
task("deploy", "Deploy a Greeter contract")
|
||||
task("deploy", "Deploy a Feedback contract")
|
||||
.addOptionalParam("semaphore", "Semaphore contract address", undefined, types.string)
|
||||
.addOptionalParam("group", "Group id", "42", types.string)
|
||||
.addOptionalParam("logs", "Print the logs", true, types.boolean)
|
||||
@@ -13,15 +13,19 @@ task("deploy", "Deploy a Greeter contract")
|
||||
semaphoreAddress = semaphore.address
|
||||
}
|
||||
|
||||
const Greeter = await ethers.getContractFactory("Greeter")
|
||||
|
||||
const greeter = await Greeter.deploy(semaphoreAddress, groupId)
|
||||
|
||||
await greeter.deployed()
|
||||
|
||||
if (logs) {
|
||||
console.info(`Greeter contract has been deployed to: ${greeter.address}`)
|
||||
if (!groupId) {
|
||||
groupId = process.env.GROUP_ID
|
||||
}
|
||||
|
||||
return greeter
|
||||
const FeedbackFactory = await ethers.getContractFactory("Feedback")
|
||||
|
||||
const feedbackContract = await FeedbackFactory.deploy(semaphoreAddress, groupId)
|
||||
|
||||
await feedbackContract.deployed()
|
||||
|
||||
if (logs) {
|
||||
console.info(`Feedback contract has been deployed to: ${feedbackContract.address}`)
|
||||
}
|
||||
|
||||
return feedbackContract
|
||||
})
|
||||
|
||||
69
packages/cli-template-hardhat/test/Feedback.ts
Normal file
69
packages/cli-template-hardhat/test/Feedback.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { Group } from "@semaphore-protocol/group"
|
||||
import { Identity } from "@semaphore-protocol/identity"
|
||||
import { generateProof } from "@semaphore-protocol/proof"
|
||||
import { expect } from "chai"
|
||||
import { formatBytes32String } from "ethers/lib/utils"
|
||||
import { run } from "hardhat"
|
||||
// @ts-ignore: typechain folder will be generated after contracts compilation
|
||||
import { Feedback } from "../build/typechain"
|
||||
import { config } from "../package.json"
|
||||
|
||||
describe("Feedback", () => {
|
||||
let feedbackContract: Feedback
|
||||
let semaphoreContract: string
|
||||
|
||||
const groupId = "42"
|
||||
const group = new Group(groupId)
|
||||
const users: Identity[] = []
|
||||
|
||||
before(async () => {
|
||||
const { semaphore } = await run("deploy:semaphore", {
|
||||
logs: false
|
||||
})
|
||||
|
||||
feedbackContract = await run("deploy", { logs: false, group: groupId, semaphore: semaphore.address })
|
||||
semaphoreContract = semaphore
|
||||
|
||||
users.push(new Identity())
|
||||
users.push(new Identity())
|
||||
})
|
||||
|
||||
describe("# joinGroup", () => {
|
||||
it("Should allow users to join the group", async () => {
|
||||
for await (const [i, user] of users.entries()) {
|
||||
const transaction = feedbackContract.joinGroup(user.commitment)
|
||||
|
||||
group.addMember(user.commitment)
|
||||
|
||||
await expect(transaction)
|
||||
.to.emit(semaphoreContract, "MemberAdded")
|
||||
.withArgs(groupId, i, user.commitment, group.root)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe("# sendFeedback", () => {
|
||||
const wasmFilePath = `${config.paths.build["snark-artifacts"]}/semaphore.wasm`
|
||||
const zkeyFilePath = `${config.paths.build["snark-artifacts"]}/semaphore.zkey`
|
||||
|
||||
it("Should allow users to send feedback anonymously", async () => {
|
||||
const feedback = formatBytes32String("Hello World")
|
||||
|
||||
const fullProof = await generateProof(users[1], group, groupId, feedback, {
|
||||
wasmFilePath,
|
||||
zkeyFilePath
|
||||
})
|
||||
|
||||
const transaction = feedbackContract.sendFeedback(
|
||||
feedback,
|
||||
fullProof.merkleTreeRoot,
|
||||
fullProof.nullifierHash,
|
||||
fullProof.proof
|
||||
)
|
||||
|
||||
await expect(transaction)
|
||||
.to.emit(semaphoreContract, "ProofVerified")
|
||||
.withArgs(groupId, fullProof.merkleTreeRoot, fullProof.nullifierHash, groupId, fullProof.signal)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,72 +0,0 @@
|
||||
import { Group } from "@semaphore-protocol/group"
|
||||
import { Identity } from "@semaphore-protocol/identity"
|
||||
import { generateProof } from "@semaphore-protocol/proof"
|
||||
import { expect } from "chai"
|
||||
import download from "download"
|
||||
import { existsSync } from "fs"
|
||||
import { ethers, run } from "hardhat"
|
||||
// @ts-ignore: typechain-types folder will be generated after contracts compilation
|
||||
import { Greeter } from "../typechain-types"
|
||||
|
||||
describe("Greeter", () => {
|
||||
let greeter: Greeter
|
||||
|
||||
const snarkArtifactsURL = "https://www.trusted-setup-pse.org/semaphore/20"
|
||||
const snarkArtifactsPath = "./artifacts/snark"
|
||||
|
||||
const users: any[] = []
|
||||
const groupId = "42"
|
||||
const group = new Group(groupId)
|
||||
|
||||
before(async () => {
|
||||
if (!existsSync(`${snarkArtifactsPath}/semaphore.wasm`)) {
|
||||
await download(`${snarkArtifactsURL}/semaphore.wasm`, `${snarkArtifactsPath}`)
|
||||
await download(`${snarkArtifactsURL}/semaphore.zkey`, `${snarkArtifactsPath}`)
|
||||
}
|
||||
|
||||
greeter = await run("deploy", { logs: false, group: groupId })
|
||||
|
||||
users.push({
|
||||
identity: new Identity(),
|
||||
username: ethers.utils.formatBytes32String("anon1")
|
||||
})
|
||||
|
||||
users.push({
|
||||
identity: new Identity(),
|
||||
username: ethers.utils.formatBytes32String("anon2")
|
||||
})
|
||||
|
||||
group.addMember(users[0].identity.commitment)
|
||||
group.addMember(users[1].identity.commitment)
|
||||
})
|
||||
|
||||
describe("# joinGroup", () => {
|
||||
it("Should allow users to join the group", async () => {
|
||||
for (let i = 0; i < group.members.length; i += 1) {
|
||||
const transaction = greeter.joinGroup(group.members[i], users[i].username)
|
||||
|
||||
await expect(transaction).to.emit(greeter, "NewUser").withArgs(group.members[i], users[i].username)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe("# greet", () => {
|
||||
it("Should allow users to greet", async () => {
|
||||
const greeting = ethers.utils.formatBytes32String("Hello World")
|
||||
|
||||
const fullProof = await generateProof(users[1].identity, group, groupId, greeting, {
|
||||
wasmFilePath: `${snarkArtifactsPath}/semaphore.wasm`,
|
||||
zkeyFilePath: `${snarkArtifactsPath}/semaphore.zkey`
|
||||
})
|
||||
|
||||
const transaction = greeter.greet(
|
||||
greeting,
|
||||
fullProof.merkleTreeRoot,
|
||||
fullProof.nullifierHash,
|
||||
fullProof.proof
|
||||
)
|
||||
|
||||
await expect(transaction).to.emit(greeter, "NewGreeting").withArgs(greeting)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,12 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2020",
|
||||
"module": "commonjs",
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"moduleResolution": "Node",
|
||||
"noImplicitAny": true,
|
||||
"resolveJsonModule": true,
|
||||
"target": "ES2018",
|
||||
"module": "CommonJS",
|
||||
"strict": true,
|
||||
"skipLibCheck": true
|
||||
"esModuleInterop": true,
|
||||
"outDir": "dist",
|
||||
"typeRoots": ["node_modules/@types", "types"]
|
||||
},
|
||||
"include": ["./tasks", "./test", "./typechain-types"],
|
||||
"include": ["scripts/**/*", "tasks/**/*", "test/**/*", "build/typechain/**/*", "types/**/*"],
|
||||
"files": ["./hardhat.config.ts"]
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@semaphore-protocol/cli",
|
||||
"type": "module",
|
||||
"version": "3.2.0",
|
||||
"version": "3.4.0",
|
||||
"description": "A command line tool to set up your Semaphore project and get group data.",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
@@ -41,7 +41,7 @@
|
||||
"typedoc": "^0.22.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"@semaphore-protocol/data": "3.2.0",
|
||||
"@semaphore-protocol/data": "3.4.0",
|
||||
"axios": "^1.3.2",
|
||||
"boxen": "^7.0.1",
|
||||
"chalk": "^5.1.2",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SemaphoreSubgraph } from "@semaphore-protocol/data"
|
||||
import { SemaphoreSubgraph, SemaphoreEthers, GroupResponse } from "@semaphore-protocol/data"
|
||||
import chalk from "chalk"
|
||||
import { program } from "commander"
|
||||
import download from "download"
|
||||
@@ -14,7 +14,7 @@ import Spinner from "./spinner.js"
|
||||
const packagePath = `${dirname(fileURLToPath(import.meta.url))}/..`
|
||||
const { description, version } = JSON.parse(readFileSync(`${packagePath}/package.json`, "utf8"))
|
||||
|
||||
const supportedNetworks = ["goerli", "mumbai", "optimism-goerli", "arbitrum"]
|
||||
const supportedNetworks = ["sepolia", "goerli", "mumbai", "optimism-goerli", "arbitrum", "arbitrum-goerli"]
|
||||
|
||||
program
|
||||
.name("semaphore")
|
||||
@@ -53,6 +53,7 @@ program
|
||||
|
||||
if (existsSync(projectDirectory)) {
|
||||
console.info(`\n ${logSymbols.error}`, `error: the '${projectDirectory}' folder already exists\n`)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -88,7 +89,7 @@ program
|
||||
|
||||
program
|
||||
.command("get-groups")
|
||||
.description("Get the list of groups from a supported network (goerli or arbitrum).")
|
||||
.description("Get the list of groups from a supported network (e.g. goerli or arbitrum).")
|
||||
.option("-n, --network <network-name>", "Supported Ethereum network.")
|
||||
.allowExcessArguments(false)
|
||||
.action(async ({ network }) => {
|
||||
@@ -105,37 +106,51 @@ program
|
||||
|
||||
if (!supportedNetworks.includes(network)) {
|
||||
console.info(`\n ${logSymbols.error}`, `error: the network '${network}' is not supported\n`)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const subgraph = new SemaphoreSubgraph(network)
|
||||
let groupIds: string[]
|
||||
|
||||
const spinner = new Spinner("Fetching groups")
|
||||
|
||||
spinner.start()
|
||||
|
||||
try {
|
||||
const groupIds = await subgraph.getGroupIds()
|
||||
const semaphoreSubgraph = new SemaphoreSubgraph(network)
|
||||
|
||||
groupIds = await semaphoreSubgraph.getGroupIds()
|
||||
|
||||
spinner.stop()
|
||||
} catch {
|
||||
try {
|
||||
const semaphoreEthers = new SemaphoreEthers(network)
|
||||
|
||||
groupIds = await semaphoreEthers.getGroupIds()
|
||||
|
||||
spinner.stop()
|
||||
} catch {
|
||||
spinner.stop()
|
||||
|
||||
console.info(`\n ${logSymbols.error}`, "error: unexpected error with the SemaphoreEthers package")
|
||||
|
||||
if (groupIds.length === 0) {
|
||||
console.info(`\n ${logSymbols.info}`, "info: there are no groups in this network\n")
|
||||
return
|
||||
}
|
||||
|
||||
const content = `\n${groupIds.map((id: any) => ` - ${id}`).join("\n")}`
|
||||
|
||||
console.info(`${content}\n`)
|
||||
} catch (error) {
|
||||
spinner.stop()
|
||||
|
||||
console.info(`\n ${logSymbols.error}`, "error: unexpected error with the Semaphore subgraph")
|
||||
}
|
||||
if (groupIds.length === 0) {
|
||||
console.info(`\n ${logSymbols.info}`, "info: there are no groups in this network\n")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const content = `\n${groupIds.map((id: any) => ` - ${id}`).join("\n")}`
|
||||
|
||||
console.info(`${content}\n`)
|
||||
})
|
||||
|
||||
program
|
||||
.command("get-group")
|
||||
.description("Get the data of a group from a supported network (Goerli or Arbitrum).")
|
||||
.description("Get the data of a group from a supported network (e.g. goerli or arbitrum).")
|
||||
.argument("[group-id]", "Identifier of the group.")
|
||||
.option("-n, --network <network-name>", "Supported Ethereum network.")
|
||||
.option("-m, --members", "Show group members.")
|
||||
@@ -150,37 +165,59 @@ program
|
||||
default: supportedNetworks[0],
|
||||
choices: supportedNetworks
|
||||
})
|
||||
|
||||
network = selectedNetwork
|
||||
}
|
||||
|
||||
if (!supportedNetworks.includes(network)) {
|
||||
console.info(`\n ${logSymbols.error}`, `error: the network '${network}' is not supported\n`)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (!groupId) {
|
||||
const subgraphGroups = new SemaphoreSubgraph(network)
|
||||
let groupIds: string[]
|
||||
|
||||
const spinnerGroups = new Spinner("Fetching groups")
|
||||
|
||||
spinnerGroups.start()
|
||||
|
||||
try {
|
||||
const groups = await subgraphGroups.getGroups()
|
||||
const semaphoreSubgraphGroups = new SemaphoreSubgraph(network)
|
||||
|
||||
groupIds = await semaphoreSubgraphGroups.getGroupIds()
|
||||
|
||||
spinnerGroups.stop()
|
||||
} catch {
|
||||
try {
|
||||
const semaphoreEthersGroups = new SemaphoreEthers(network)
|
||||
|
||||
groupIds = await semaphoreEthersGroups.getGroupIds()
|
||||
|
||||
spinnerGroups.stop()
|
||||
} catch {
|
||||
spinnerGroups.stop()
|
||||
|
||||
console.info(`\n ${logSymbols.error}`, "error: unexpected error with the SemaphoreEthers package")
|
||||
|
||||
if (groups.length === 0) {
|
||||
console.info(`\n ${logSymbols.info}`, "info: there are no groups in this network\n")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const groupIds = groups.map(({ id }: any) => id)
|
||||
if (groupIds.length === 0) {
|
||||
console.info(`\n ${logSymbols.info}`, "info: there are no groups in this network\n")
|
||||
|
||||
const { selectedGroupId } = await inquirer.prompt({
|
||||
name: "selectedGroupId",
|
||||
type: "list",
|
||||
message: "Select one of the following existing group ids:",
|
||||
choices: groupIds
|
||||
})
|
||||
groupId = selectedGroupId
|
||||
} catch (error) {
|
||||
spinnerGroups.stop()
|
||||
console.info(`\n ${logSymbols.error}`, "error: unexpected error with the Semaphore subgraph")
|
||||
return
|
||||
}
|
||||
|
||||
const { selectedGroupId } = await inquirer.prompt({
|
||||
name: "selectedGroupId",
|
||||
type: "list",
|
||||
message: "Select one of the following existing group ids:",
|
||||
choices: groupIds
|
||||
})
|
||||
|
||||
groupId = selectedGroupId
|
||||
}
|
||||
|
||||
if (!members && !signals) {
|
||||
@@ -203,53 +240,65 @@ program
|
||||
signals = showSignals
|
||||
}
|
||||
|
||||
if (!supportedNetworks.includes(network)) {
|
||||
console.info(`\n ${logSymbols.error}`, `error: the network '${network}' is not supported\n`)
|
||||
return
|
||||
}
|
||||
let group: GroupResponse
|
||||
|
||||
const subgraph = new SemaphoreSubgraph(network)
|
||||
const spinner = new Spinner(`Fetching group ${groupId}`)
|
||||
|
||||
spinner.start()
|
||||
|
||||
try {
|
||||
const group = await subgraph.getGroup(groupId, { members, verifiedProofs: signals })
|
||||
const semaphoreSubgraph = new SemaphoreSubgraph(network)
|
||||
|
||||
group = await semaphoreSubgraph.getGroup(groupId, { members, verifiedProofs: signals })
|
||||
|
||||
spinner.stop()
|
||||
} catch {
|
||||
try {
|
||||
const semaphoreEthers = new SemaphoreEthers(network)
|
||||
|
||||
group = await semaphoreEthers.getGroup(groupId)
|
||||
|
||||
if (members) {
|
||||
group.members = await semaphoreEthers.getGroupMembers(groupId)
|
||||
}
|
||||
|
||||
if (signals) {
|
||||
group.verifiedProofs = await semaphoreEthers.getGroupVerifiedProofs(groupId)
|
||||
}
|
||||
|
||||
group.admin = await semaphoreEthers.getGroupAdmin(groupId)
|
||||
|
||||
spinner.stop()
|
||||
} catch {
|
||||
spinner.stop()
|
||||
|
||||
if (!group) {
|
||||
console.info(`\n ${logSymbols.error}`, "error: the group does not exist\n")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
let content = ` ${chalk.bold("Id")}: ${group.id}\n`
|
||||
content += ` ${chalk.bold("Admin")}: ${group.admin}\n`
|
||||
content += ` ${chalk.bold("Merkle tree")}:\n`
|
||||
content += ` Root: ${group.merkleTree.root}\n`
|
||||
content += ` Depth: ${group.merkleTree.depth}\n`
|
||||
content += ` Zero value: ${group.merkleTree.zeroValue}\n`
|
||||
content += ` Number of leaves: ${group.merkleTree.numberOfLeaves}`
|
||||
|
||||
if (members) {
|
||||
content += `\n\n ${chalk.bold("Members")}: \n${group.members
|
||||
.map((member: string, i: number) => ` ${i}. ${member}`)
|
||||
.join("\n")}`
|
||||
}
|
||||
|
||||
if (signals) {
|
||||
content += `\n\n ${chalk.bold("Signals")}: \n${group.verifiedProofs
|
||||
.map(({ signal }: any) => ` - ${signal}`)
|
||||
.join("\n")}`
|
||||
}
|
||||
|
||||
console.info(`\n${content}\n`)
|
||||
} catch (error) {
|
||||
spinner.stop()
|
||||
|
||||
console.info(`\n ${logSymbols.error}`, "error: unexpected error with the Semaphore subgraph")
|
||||
}
|
||||
|
||||
let content = ` ${chalk.bold("Id")}: ${group.id}\n`
|
||||
content += ` ${chalk.bold("Admin")}: ${group.admin}\n`
|
||||
content += ` ${chalk.bold("Merkle tree")}:\n`
|
||||
content += ` Root: ${group.merkleTree.root}\n`
|
||||
content += ` Depth: ${group.merkleTree.depth}\n`
|
||||
content += ` Zero value: ${group.merkleTree.zeroValue}\n`
|
||||
content += ` Number of leaves: ${group.merkleTree.numberOfLeaves}`
|
||||
|
||||
if (members) {
|
||||
content += `\n\n ${chalk.bold("Members")}: \n${group.members
|
||||
.map((member: string, i: number) => ` ${i}. ${member}`)
|
||||
.join("\n")}`
|
||||
}
|
||||
|
||||
if (signals) {
|
||||
content += `\n\n ${chalk.bold("Signals")}: \n${group.verifiedProofs
|
||||
.map(({ signal }: any) => ` - ${signal}`)
|
||||
.join("\n")}`
|
||||
}
|
||||
|
||||
console.info(`\n${content}\n`)
|
||||
})
|
||||
|
||||
program.parse(process.argv)
|
||||
|
||||
@@ -14,7 +14,7 @@ library Pairing {
|
||||
// The prime q in the base field F_q for G1
|
||||
uint256 constant BASE_MODULUS = 21888242871839275222246405745257275088696311157297823662689037894645226208583;
|
||||
|
||||
// The prime moludus of the scalar field of G1.
|
||||
// The prime modulus of the scalar field of G1.
|
||||
uint256 constant SCALAR_MODULUS = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
|
||||
|
||||
struct G1Point {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
{
|
||||
"name": "@semaphore-protocol/contracts",
|
||||
"version": "3.2.0",
|
||||
"version": "3.4.0",
|
||||
"description": "Semaphore contracts to manage groups and broadcast anonymous signals.",
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"Semaphore.sol",
|
||||
"**/*.sol"
|
||||
],
|
||||
"keywords": [
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"Pairing": "0xC0ae1a8D3505B2bE9DCe0e826abd722Afd13F1c9",
|
||||
"SemaphoreVerifier": "0x346a936b19071b2f619200848B8ADbb938D72250",
|
||||
"Poseidon": "0xb69aABB5D8d8e4920834761bD0C9DEEfa5D5502F",
|
||||
"IncrementalBinaryTree": "0x9f44be9F69aF1e049dCeCDb2d9296f36C49Ceafb",
|
||||
"Semaphore": "0xbE66454b0Fa9E6b3D53DC1b0f9D21978bb864531"
|
||||
}
|
||||
@@ -45,8 +45,13 @@ function getNetworks(): NetworksUserConfig {
|
||||
chainId: 420,
|
||||
accounts
|
||||
},
|
||||
"arbitrum-goerli": {
|
||||
url: "https://goerli-rollup.arbitrum.io/rpc",
|
||||
chainId: 421613,
|
||||
accounts
|
||||
},
|
||||
arbitrum: {
|
||||
url: `https://arbitrum-mainnet.infura.io/v3/${infuraApiKey}`,
|
||||
url: "https://arb1.arbitrum.io/rpc",
|
||||
chainId: 42161,
|
||||
accounts
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@semaphore-protocol/data",
|
||||
"version": "3.2.0",
|
||||
"version": "3.4.0",
|
||||
"description": "A library to query Semaphore contracts.",
|
||||
"license": "MIT",
|
||||
"main": "dist/index.node.js",
|
||||
|
||||
@@ -28,17 +28,23 @@ describe("SemaphoreEthers", () => {
|
||||
it("Should instantiate a SemaphoreEthers object with different networks", () => {
|
||||
semaphore = new SemaphoreEthers()
|
||||
const semaphore1 = new SemaphoreEthers("arbitrum")
|
||||
const semaphore2 = new SemaphoreEthers("homestead", {
|
||||
const semaphore2 = new SemaphoreEthers("matic")
|
||||
const semaphore3 = new SemaphoreEthers("optimism-goerli")
|
||||
const semaphore4 = new SemaphoreEthers("arbitrum-goerli")
|
||||
const semaphore5 = new SemaphoreEthers("homestead", {
|
||||
address: "0x0000000000000000000000000000000000000000",
|
||||
startBlock: 0
|
||||
})
|
||||
|
||||
expect(semaphore.network).toBe("goerli")
|
||||
expect(semaphore.network).toBe("sepolia")
|
||||
expect(semaphore.contract).toBeInstanceOf(Object)
|
||||
expect(semaphore1.network).toBe("arbitrum")
|
||||
expect(semaphore2.network).toBe("homestead")
|
||||
expect(semaphore2.options.startBlock).toBe(0)
|
||||
expect(semaphore2.options.address).toContain("0x000000")
|
||||
expect(semaphore2.network).toBe("maticmum")
|
||||
expect(semaphore3.network).toBe("optimism-goerli")
|
||||
expect(semaphore4.network).toBe("arbitrum-goerli")
|
||||
expect(semaphore5.network).toBe("homestead")
|
||||
expect(semaphore5.options.startBlock).toBe(0)
|
||||
expect(semaphore5.options.address).toContain("0x000000")
|
||||
})
|
||||
|
||||
it("Should instantiate a SemaphoreEthers object with different providers", () => {
|
||||
@@ -249,6 +255,14 @@ describe("SemaphoreEthers", () => {
|
||||
|
||||
describe("# getGroupVerifiedProofs", () => {
|
||||
it("Should return a list of group verified proofs", async () => {
|
||||
getEventsMocked.mockReturnValueOnce(
|
||||
Promise.resolve([
|
||||
{
|
||||
merkleTreeDepth: "20",
|
||||
zeroValue: "0"
|
||||
}
|
||||
])
|
||||
)
|
||||
getEventsMocked.mockReturnValueOnce(
|
||||
Promise.resolve([
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@ export default class SemaphoreEthers {
|
||||
* @param networkOrEthereumURL Ethereum network or custom URL.
|
||||
* @param options Ethers options.
|
||||
*/
|
||||
constructor(networkOrEthereumURL: Network | string = "goerli", options: EthersOptions = {}) {
|
||||
constructor(networkOrEthereumURL: Network | string = "sepolia", options: EthersOptions = {}) {
|
||||
checkParameter(networkOrEthereumURL, "networkOrSubgraphURL", "string")
|
||||
|
||||
if (options.provider) {
|
||||
@@ -46,6 +46,10 @@ export default class SemaphoreEthers {
|
||||
options.address = "0x72dca3c971136bf47BACF16A141f0fcfAC925aeC"
|
||||
options.startBlock = 54934350
|
||||
break
|
||||
case "arbitrum-goerli":
|
||||
options.address = "0xbE66454b0Fa9E6b3D53DC1b0f9D21978bb864531"
|
||||
options.startBlock = 11902029
|
||||
break
|
||||
case "maticmum":
|
||||
options.address = "0xF864ABa335073e01234c9a88888BfFfa965650bD"
|
||||
options.startBlock = 32902215
|
||||
@@ -54,6 +58,14 @@ export default class SemaphoreEthers {
|
||||
options.address = "0x89490c95eD199D980Cdb4FF8Bac9977EDb41A7E7"
|
||||
options.startBlock = 8255063
|
||||
break
|
||||
case "sepolia":
|
||||
options.address = "0x220fBdB6F996827b1Cf12f0C181E8d5e6de3a36F"
|
||||
options.startBlock = 3053948
|
||||
break
|
||||
case "optimism-goerli":
|
||||
options.address = "0x220fBdB6F996827b1Cf12f0C181E8d5e6de3a36F"
|
||||
options.startBlock = 6477953
|
||||
break
|
||||
default:
|
||||
if (options.address === undefined) {
|
||||
throw new Error(`You should provide a Semaphore contract address for this network`)
|
||||
@@ -246,6 +258,12 @@ export default class SemaphoreEthers {
|
||||
async getGroupVerifiedProofs(groupId: string): Promise<any> {
|
||||
checkParameter(groupId, "groupId", "string")
|
||||
|
||||
const [groupCreatedEvent] = await getEvents(this._contract, "GroupCreated", [groupId], this._options.startBlock)
|
||||
|
||||
if (!groupCreatedEvent) {
|
||||
throw new Error(`Group '${groupId}' not found`)
|
||||
}
|
||||
|
||||
const proofVerifiedEvents = await getEvents(
|
||||
this._contract,
|
||||
"ProofVerified",
|
||||
@@ -253,10 +271,6 @@ export default class SemaphoreEthers {
|
||||
this._options.startBlock
|
||||
)
|
||||
|
||||
if (proofVerifiedEvents.length === 0) {
|
||||
throw new Error(`Group '${groupId}' not found`)
|
||||
}
|
||||
|
||||
return proofVerifiedEvents.map((event) => ({
|
||||
signal: event.signal.toString(),
|
||||
merkleTreeRoot: event.merkleTreeRoot.toString(),
|
||||
|
||||
@@ -10,6 +10,7 @@ export default function getURL(network: Network): string {
|
||||
case "goerli":
|
||||
case "mumbai":
|
||||
case "optimism-goerli":
|
||||
case "arbitrum-goerli":
|
||||
case "arbitrum":
|
||||
return `https://api.studio.thegraph.com/query/14377/semaphore-${network}/v3.2.0`
|
||||
default:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@semaphore-protocol/group",
|
||||
"version": "3.2.0",
|
||||
"version": "3.4.0",
|
||||
"description": "A library to create and manage Semaphore groups.",
|
||||
"license": "MIT",
|
||||
"main": "dist/index.node.js",
|
||||
@@ -30,6 +30,9 @@
|
||||
"access": "public"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^24.0.1",
|
||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||
"poseidon-lite": "^0.1.0",
|
||||
"rollup-plugin-cleanup": "^3.2.1",
|
||||
"rollup-plugin-typescript2": "^0.31.2",
|
||||
"typedoc": "^0.22.11"
|
||||
@@ -38,7 +41,6 @@
|
||||
"@ethersproject/bignumber": "^5.7.0",
|
||||
"@ethersproject/bytes": "^5.7.0",
|
||||
"@ethersproject/keccak256": "^5.7.0",
|
||||
"@zk-kit/incremental-merkle-tree": "1.0.0",
|
||||
"poseidon-lite": "^0.1.0"
|
||||
"@zk-kit/incremental-merkle-tree": "1.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import typescript from "rollup-plugin-typescript2"
|
||||
import commonjs from "@rollup/plugin-commonjs"
|
||||
import { nodeResolve } from "@rollup/plugin-node-resolve"
|
||||
import * as fs from "fs"
|
||||
import cleanup from "rollup-plugin-cleanup"
|
||||
import typescript from "rollup-plugin-typescript2"
|
||||
|
||||
const pkg = JSON.parse(fs.readFileSync("./package.json", "utf-8"))
|
||||
const banner = `/**
|
||||
@@ -24,6 +26,8 @@ export default {
|
||||
tsconfig: "./build.tsconfig.json",
|
||||
useTsconfigDeclarationDir: true
|
||||
}),
|
||||
commonjs(),
|
||||
nodeResolve(),
|
||||
cleanup({ comments: "jsdoc" })
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@semaphore-protocol/hardhat",
|
||||
"version": "3.2.0",
|
||||
"version": "3.4.0",
|
||||
"description": "A Semaphore Hardhat plugin to deploy verifiers and Semaphore contract.",
|
||||
"license": "MIT",
|
||||
"main": "dist/index.node.js",
|
||||
@@ -38,7 +38,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@nomiclabs/hardhat-ethers": "^2.1.1",
|
||||
"@semaphore-protocol/contracts": "3.2.0",
|
||||
"@semaphore-protocol/contracts": "3.4.0",
|
||||
"circomlibjs": "^0.0.8",
|
||||
"ethers": "^5.7.1",
|
||||
"hardhat-dependency-compiler": "^1.1.3"
|
||||
|
||||
21
packages/heyauthn/LICENSE
Normal file
21
packages/heyauthn/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Vivek Bhupatiraju
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
118
packages/heyauthn/README.md
Normal file
118
packages/heyauthn/README.md
Normal file
@@ -0,0 +1,118 @@
|
||||
<p align="center">
|
||||
<h1 align="center">
|
||||
HeyAuthn
|
||||
</h1>
|
||||
<p align="center">A library to allow developers to create and manage Semaphore identities using WebAuthn.</p>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/semaphore-protocol">
|
||||
<img src="https://img.shields.io/badge/project-Semaphore-blue.svg?style=flat-square">
|
||||
</a>
|
||||
<a href="https://github.com/semaphore-protocol/semaphore/blob/main/LICENSE">
|
||||
<img alt="Github license" src="https://img.shields.io/github/license/semaphore-protocol/semaphore.svg?style=flat-square">
|
||||
</a>
|
||||
<a href="https://www.npmjs.com/package/@semaphore-protocol/heyauthn">
|
||||
<img alt="NPM version" src="https://img.shields.io/npm/v/@semaphore-protocol/heyauthn?style=flat-square" />
|
||||
</a>
|
||||
<a href="https://npmjs.org/package/@semaphore-protocol/heyauthn">
|
||||
<img alt="Downloads" src="https://img.shields.io/npm/dm/@semaphore-protocol/heyauthn.svg?style=flat-square" />
|
||||
</a>
|
||||
<a href="https://eslint.org/">
|
||||
<img alt="Linter eslint" src="https://img.shields.io/badge/linter-eslint-8080f2?style=flat-square&logo=eslint" />
|
||||
</a>
|
||||
<a href="https://prettier.io/">
|
||||
<img alt="Code style prettier" src="https://img.shields.io/badge/code%20style-prettier-f8bc45?style=flat-square&logo=prettier" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<div align="center">
|
||||
<h4>
|
||||
<a href="https://github.com/semaphore-protocol/semaphore/blob/main/CONTRIBUTING.md">
|
||||
👥 Contributing
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/semaphore-protocol/semaphore/blob/main/CODE_OF_CONDUCT.md">
|
||||
🤝 Code of conduct
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/semaphore-protocol/semaphore/contribute">
|
||||
🔎 Issues
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://semaphore.appliedzkp.org/discord">
|
||||
🗣️ Chat & Support
|
||||
</a>
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
| This library allows developers to create and manage Semaphore identities using [WebAuthn](https://webauthn.io/) as a cross-device biometric authentication in a way that is more convenient, smoother and secure than localStorage, Chrome extensions, or password manager based solutions. |
|
||||
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
## 🛠 Install
|
||||
|
||||
### npm or yarn
|
||||
|
||||
Install the `@semaphore-protocol/heyauthn` package with npm:
|
||||
|
||||
```bash
|
||||
npm i @semaphore-protocol/heyauthn
|
||||
```
|
||||
|
||||
or yarn:
|
||||
|
||||
```bash
|
||||
yarn add @semaphore-protocol/heyauthn
|
||||
```
|
||||
|
||||
## 📜 Usage
|
||||
|
||||
```typescript
|
||||
import { HeyAuthn } from "@semaphore-protocol/heyauthn"
|
||||
|
||||
// STEP 1: Configure WebAuthn options.
|
||||
|
||||
const options = {
|
||||
rpName: "my-app",
|
||||
rpID: window.location.hostname,
|
||||
userID: "my-id",
|
||||
userName: "my-name"
|
||||
}
|
||||
|
||||
// STEP 2: Register a new WebAuthn credential and get its Semaphore identity.
|
||||
|
||||
const { identity } = await HeyAuthn.fromRegister(options)
|
||||
|
||||
// Now you could also save the identity commitment in your DB (pseudocode).
|
||||
fetch("/api/register" /* Replace this with your endpoint */, {
|
||||
identity.commmitment
|
||||
// ...
|
||||
})
|
||||
|
||||
// STEP 3: Authenticate existing WebAuthn credential and signal.
|
||||
|
||||
const { identity } = await HeyAuthn.fromRegister(options)
|
||||
|
||||
// Get existing group and signal anonymously (pseudocode).
|
||||
import { Group } from "@semaphore-protocol/group"
|
||||
import { generateProof } from "@semaphore-protocol/proof"
|
||||
import { utils } from "ethers"
|
||||
|
||||
const group = new Group("42")
|
||||
|
||||
group.addMembers(memberList)
|
||||
|
||||
const signal = utils.formatBytes32String("Hey anon!")
|
||||
|
||||
generateProof(identity, group, group.id, "42", {
|
||||
zkeyFilePath: "./semaphore.zkey",
|
||||
wasmFilePath: "./semaphore.wasm"
|
||||
})
|
||||
```
|
||||
|
||||
## Authors
|
||||
|
||||
- @vb7401
|
||||
- @rrrliu
|
||||
- @emmaguo13
|
||||
- @sehyunc
|
||||
8
packages/heyauthn/build.tsconfig.json
Normal file
8
packages/heyauthn/build.tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"declarationDir": "dist/types"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
42
packages/heyauthn/package.json
Normal file
42
packages/heyauthn/package.json
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "@semaphore-protocol/heyauthn",
|
||||
"version": "3.4.0",
|
||||
"description": "A library to allow developers to create and manage Semaphore identities using WebAuthn",
|
||||
"license": "MIT",
|
||||
"main": "dist/index.node.js",
|
||||
"exports": {
|
||||
"import": "./dist/index.mjs",
|
||||
"require": "./dist/index.node.js"
|
||||
},
|
||||
"types": "dist/types/index.d.ts",
|
||||
"files": [
|
||||
"dist/",
|
||||
"src/",
|
||||
"LICENSE",
|
||||
"README.md"
|
||||
],
|
||||
"repository": "https://github.com/semaphore-protocol/semaphore",
|
||||
"homepage": "https://github.com/semaphore-protocol/semaphore/tree/main/packages/heyauthn",
|
||||
"bugs": {
|
||||
"url": "https://github.com/semaphore-protocol/semaphore.git/issues"
|
||||
},
|
||||
"scripts": {
|
||||
"build:watch": "rollup -c rollup.config.ts -w --configPlugin typescript",
|
||||
"build": "rimraf dist && rollup -c rollup.config.ts --configPlugin typescript",
|
||||
"prepublishOnly": "yarn build",
|
||||
"docs": "typedoc src/index.ts --out ../../docs/heyauthn"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"devDependencies": {
|
||||
"rollup-plugin-cleanup": "^3.2.1",
|
||||
"rollup-plugin-typescript2": "^0.31.2",
|
||||
"typedoc": "^0.22.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"@semaphore-protocol/identity": "3.4.0",
|
||||
"@simplewebauthn/browser": "7.2.0",
|
||||
"@simplewebauthn/server": "7.2.0"
|
||||
}
|
||||
}
|
||||
29
packages/heyauthn/rollup.config.ts
Normal file
29
packages/heyauthn/rollup.config.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import typescript from "rollup-plugin-typescript2"
|
||||
import * as fs from "fs"
|
||||
import cleanup from "rollup-plugin-cleanup"
|
||||
|
||||
const pkg = JSON.parse(fs.readFileSync("./package.json", "utf-8"))
|
||||
const banner = `/**
|
||||
* @module ${pkg.name}
|
||||
* @version ${pkg.version}
|
||||
* @file ${pkg.description}
|
||||
* @copyright Vivek Bhupatiraju 2023
|
||||
* @license ${pkg.license}
|
||||
* @see [Github]{@link ${pkg.homepage}}
|
||||
*/`
|
||||
|
||||
export default {
|
||||
input: "src/index.ts",
|
||||
output: [
|
||||
{ file: pkg.exports.require, format: "cjs", banner, exports: "auto" },
|
||||
{ file: pkg.exports.import, format: "es", banner }
|
||||
],
|
||||
external: Object.keys(pkg.dependencies),
|
||||
plugins: [
|
||||
typescript({
|
||||
tsconfig: "./build.tsconfig.json",
|
||||
useTsconfigDeclarationDir: true
|
||||
}),
|
||||
cleanup({ comments: "jsdoc" })
|
||||
]
|
||||
}
|
||||
75
packages/heyauthn/src/heyAuthn.test.ts
Normal file
75
packages/heyauthn/src/heyAuthn.test.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { Identity } from "@semaphore-protocol/identity"
|
||||
import {
|
||||
GenerateAuthenticationOptionsOpts as AuthenticationOptions,
|
||||
GenerateRegistrationOptionsOpts as RegistrationOptions
|
||||
} from "@simplewebauthn/server"
|
||||
|
||||
import HeyAuthn from "./heyAuthn"
|
||||
|
||||
jest.mock("@simplewebauthn/browser", () => ({
|
||||
startRegistration: async () => ({
|
||||
id: "my-new-credential",
|
||||
rawId: "my-new-credential",
|
||||
response: {
|
||||
clientDataJSON: "",
|
||||
attestationObject: ""
|
||||
},
|
||||
clientExtensionResults: {},
|
||||
type: "public-key"
|
||||
}),
|
||||
startAuthentication: async () => ({
|
||||
id: "my-existing-credential",
|
||||
rawId: "my-existing-credential",
|
||||
response: {
|
||||
clientDataJSON: "",
|
||||
attestationObject: ""
|
||||
},
|
||||
clientExtensionResults: {},
|
||||
type: "public-key"
|
||||
})
|
||||
}))
|
||||
|
||||
describe("HeyAuthn", () => {
|
||||
describe("# getIdentity", () => {
|
||||
it("Should get the identity of the HeyAuthn instance", async () => {
|
||||
const expectedIdentity = new Identity()
|
||||
const heyAuthn = new HeyAuthn(expectedIdentity)
|
||||
const identity = heyAuthn.getIdentity()
|
||||
|
||||
expect(expectedIdentity.toString()).toEqual(identity.toString())
|
||||
})
|
||||
})
|
||||
|
||||
describe("# fromRegister", () => {
|
||||
const options: RegistrationOptions = {
|
||||
rpName: "my-app",
|
||||
rpID: "hostname",
|
||||
userID: "my-id",
|
||||
userName: "my-name"
|
||||
}
|
||||
|
||||
it("Should create an identity identical to the one created registering credential", async () => {
|
||||
const { identity } = await HeyAuthn.fromRegister(options)
|
||||
const expectedIdentity = new Identity("my-new-credential")
|
||||
|
||||
expect(expectedIdentity.trapdoor).toEqual(identity.trapdoor)
|
||||
expect(expectedIdentity.nullifier).toEqual(identity.nullifier)
|
||||
expect(expectedIdentity.commitment).toEqual(identity.commitment)
|
||||
})
|
||||
})
|
||||
|
||||
describe("# fromAuthenticate", () => {
|
||||
const options: AuthenticationOptions = {
|
||||
rpID: "hostname"
|
||||
}
|
||||
|
||||
it("Should create an identity identical to the one created authenticating credential", async () => {
|
||||
const { identity } = await HeyAuthn.fromAuthenticate(options)
|
||||
const expectedIdentity = new Identity("my-existing-credential")
|
||||
|
||||
expect(expectedIdentity.trapdoor).toEqual(identity.trapdoor)
|
||||
expect(expectedIdentity.nullifier).toEqual(identity.nullifier)
|
||||
expect(expectedIdentity.commitment).toEqual(identity.commitment)
|
||||
})
|
||||
})
|
||||
})
|
||||
62
packages/heyauthn/src/heyAuthn.ts
Normal file
62
packages/heyauthn/src/heyAuthn.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import {
|
||||
generateAuthenticationOptions,
|
||||
generateRegistrationOptions,
|
||||
GenerateRegistrationOptionsOpts as RegistrationOptions,
|
||||
GenerateAuthenticationOptionsOpts as AuthenticationOptions
|
||||
} from "@simplewebauthn/server"
|
||||
import { startAuthentication, startRegistration } from "@simplewebauthn/browser"
|
||||
import { Identity } from "@semaphore-protocol/identity"
|
||||
|
||||
export default class HeyAuthn {
|
||||
private _identity: Identity
|
||||
|
||||
constructor(identity: Identity) {
|
||||
this._identity = identity
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new WebAuthn credential and returns its HeyAuthn instance.
|
||||
*
|
||||
* @param {GenerateRegistrationOptionsOpts} options - WebAuthn options for registering a new credential.
|
||||
* @returns A HeyAuthn instance with the newly registered credential.
|
||||
*/
|
||||
public static async fromRegister(options: RegistrationOptions) {
|
||||
const registrationOptions = generateRegistrationOptions(options)
|
||||
const { id } = await startRegistration(registrationOptions)
|
||||
|
||||
const identity = new Identity(id)
|
||||
|
||||
return new HeyAuthn(identity)
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticates an existing WebAuthn credential and returns its HeyAuthn instance.
|
||||
*
|
||||
* @param {GenerateAuthenticationOptionsOpts} options - WebAuthn options for authenticating an existing credential.
|
||||
* @returns A HeyAuthn instance with the existing credential.
|
||||
*/
|
||||
public static async fromAuthenticate(options: AuthenticationOptions) {
|
||||
const authenticationOptions = generateAuthenticationOptions(options)
|
||||
const { id } = await startAuthentication(authenticationOptions)
|
||||
|
||||
const identity = new Identity(id)
|
||||
|
||||
return new HeyAuthn(identity)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Semaphore identity instance.
|
||||
* @returns The Semaphore identity.
|
||||
*/
|
||||
public get identity(): Identity {
|
||||
return this._identity
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Semaphore identity instance.
|
||||
* @returns The Semaphore identity.
|
||||
*/
|
||||
public getIdentity(): Identity {
|
||||
return this._identity
|
||||
}
|
||||
}
|
||||
10
packages/heyauthn/src/index.ts
Normal file
10
packages/heyauthn/src/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Identity } from "@semaphore-protocol/identity"
|
||||
import { GenerateAuthenticationOptionsOpts, GenerateRegistrationOptionsOpts } from "@simplewebauthn/server"
|
||||
import HeyAuthn from "./heyAuthn"
|
||||
|
||||
export {
|
||||
HeyAuthn,
|
||||
GenerateRegistrationOptionsOpts as RegistrationOptions,
|
||||
GenerateAuthenticationOptionsOpts as AuthenticationOptions,
|
||||
Identity
|
||||
}
|
||||
4
packages/heyauthn/tsconfig.json
Normal file
4
packages/heyauthn/tsconfig.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"include": ["src", "rollup.config.ts"]
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@semaphore-protocol/identity",
|
||||
"version": "3.2.0",
|
||||
"version": "3.4.0",
|
||||
"description": "A library to create Semaphore identities.",
|
||||
"license": "MIT",
|
||||
"main": "dist/index.node.js",
|
||||
@@ -30,6 +30,9 @@
|
||||
"access": "public"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^24.0.1",
|
||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||
"poseidon-lite": "^0.1.0",
|
||||
"rollup-plugin-cleanup": "^3.2.1",
|
||||
"rollup-plugin-typescript2": "^0.31.2",
|
||||
"typedoc": "^0.22.11"
|
||||
@@ -39,7 +42,6 @@
|
||||
"@ethersproject/keccak256": "^5.7.0",
|
||||
"@ethersproject/random": "^5.5.1",
|
||||
"@ethersproject/strings": "^5.6.1",
|
||||
"js-sha512": "^0.8.0",
|
||||
"poseidon-lite": "^0.1.0"
|
||||
"js-sha512": "^0.8.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import typescript from "rollup-plugin-typescript2"
|
||||
import commonjs from "@rollup/plugin-commonjs"
|
||||
import * as fs from "fs"
|
||||
import cleanup from "rollup-plugin-cleanup"
|
||||
import { nodeResolve } from "@rollup/plugin-node-resolve"
|
||||
|
||||
const pkg = JSON.parse(fs.readFileSync("./package.json", "utf-8"))
|
||||
const banner = `/**
|
||||
@@ -24,6 +26,8 @@ export default {
|
||||
tsconfig: "./build.tsconfig.json",
|
||||
useTsconfigDeclarationDir: true
|
||||
}),
|
||||
commonjs(),
|
||||
nodeResolve(),
|
||||
cleanup({ comments: "jsdoc" })
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@semaphore-protocol/proof",
|
||||
"version": "3.2.0",
|
||||
"version": "3.4.0",
|
||||
"description": "A library to generate and verify Semaphore proofs.",
|
||||
"license": "MIT",
|
||||
"main": "dist/index.node.js",
|
||||
@@ -37,8 +37,8 @@
|
||||
"typedoc": "^0.22.11"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@semaphore-protocol/group": "3.2.0",
|
||||
"@semaphore-protocol/identity": "3.2.0"
|
||||
"@semaphore-protocol/group": "3.4.0",
|
||||
"@semaphore-protocol/identity": "3.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ethersproject/bignumber": "^5.5.0",
|
||||
|
||||
@@ -1 +1 @@
|
||||
37fdb99af156c475e680a24b069baea47cf69d07
|
||||
749e590bb5a7dbd0f505b3b8f4e68dea5c991395
|
||||
Reference in New Issue
Block a user