Compare commits

...

25 Commits

Author SHA1 Message Date
cedoor
46fe2fc8f8 chore: v3.3.0
Former-commit-id: af89f3a0a1
2023-03-21 19:28:09 +00:00
cedoor
7bb9554388 style(heyauthn): format code with prettier
Former-commit-id: 587e3045c8
2023-03-21 19:22:22 +00:00
Cedoor
d88a71ec4d Merge pull request #285 from semaphore-protocol/feat/heyauthn
New HeyAuthn package

Former-commit-id: b497be753e
2023-03-21 19:17:11 +00:00
cedoor
2afc4ce1de style(cli): format code
Former-commit-id: 22ee1e0109
2023-03-21 11:36:42 +00:00
Cedoor
64f7b24c53 Merge pull request #281 from semaphore-protocol/feat/cli-sepolia-network
Add support for Sepolia network in the CLI

Former-commit-id: 889fe89162
2023-03-21 11:19:44 +00:00
Cedoor
88ba0af2d2 Merge pull request #282 from semaphore-protocol/chore/arbitrum-goerli
Semaphore support for Arbitrum Goerli network

Former-commit-id: 0a6061c9c5
2023-03-21 11:05:31 +00:00
vplasencia
c2e8ba6856 fix(cli): add admin in get-group when using SemaphoreEthers
Former-commit-id: 4641735932
2023-03-21 01:15:07 +01:00
cedoor
f9a8d68641 docs(heyauthn): add authors to readme file
re #284


Former-commit-id: 53637009b1
2023-03-20 16:37:55 +00:00
cedoor
df84100c22 feat(heyauthn): create heyauthn package
re #284


Former-commit-id: f67c1f07d7
2023-03-20 16:29:26 +00:00
cedoor
b962339203 feat(data): add support for arbitrum goerli network
re #275


Former-commit-id: 0571bbcdaa
2023-03-20 12:42:52 +00:00
Cedoor
073f5a5772 Merge pull request #283 from semaphore-protocol/feat/cli-template-hardhat
Add new version of cli-template-hardhat

Former-commit-id: e44ca7d447
2023-03-20 12:18:09 +00:00
vplasencia
b26f74a453 feat(cli-template-hardhat): add new version of cli-template-hardhat
Former-commit-id: 4133ae12c3
2023-03-20 12:36:12 +01:00
vplasencia
e9cac671f2 refactor(cli): remove unnecessary code in get-group command
Former-commit-id: 716a20cb7c
2023-03-17 23:11:40 +01:00
vplasencia
ecf8dcafb1 refactor(cli): remove unused code
Former-commit-id: 4ecf293cd4
2023-03-17 22:59:41 +01:00
vplasencia
f90c99193a refactor(cli): organize get-groups command code
Former-commit-id: eefffca9f4
2023-03-17 22:55:40 +01:00
vplasencia
a826708320 feat(cli): update get-group command to use sepolia
Former-commit-id: 4ddf75b378
2023-03-17 22:41:11 +01:00
cedoor
43370202a7 style(contracts): format code with prettier
Former-commit-id: ef393e9c03
2023-03-17 18:07:53 +00:00
cedoor
a4d1180d26 chore(contracts): deploy semaphore on arbitrum goerli network
re #275


Former-commit-id: e34ce9c61a
2023-03-17 18:01:40 +00:00
vplasencia
5c42f9e09c feat(cli): add support for sepolia network in the cli
Former-commit-id: 63666f6a9c
2023-03-17 14:27:58 +01:00
Cedoor
c8362e373b Merge pull request #280 from dbrans/patch-1
Comment typo in Pairing.sol

Former-commit-id: 032702b245
2023-03-17 10:53:27 +00:00
Derek Brans
06765f2f88 Fix typo in Pairing.sol
Former-commit-id: 0810ffbe0f
2023-03-16 19:15:42 -05:00
Cedoor
f26c84445e Merge pull request #279 from semaphore-protocol/chore/default-network
New default network (Sepolia)

Former-commit-id: 6c644522f0
2023-03-16 10:42:46 +00:00
cedoor
61a2d6adc2 test(data): update tests for semaphore ethers class
re #277


Former-commit-id: 8988f478c9
2023-03-15 19:04:23 +00:00
cedoor
eddd6b3dd5 chore(data): change default network
re #277


Former-commit-id: 49221b6ba7
2023-03-15 18:56:49 +00:00
cedoor
fa561f8f00 chore(cli): update command description
Former-commit-id: a4f9e47957
2023-03-15 18:55:34 +00:00
34 changed files with 759 additions and 261 deletions

View File

@@ -1,4 +1,4 @@
ETHEREUM_URL=
INFURA_API_KEY=
ETHEREUM_PRIVATE_KEY=
REPORT_GAS=false
COINMARKETCAP_API_KEY=

View 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);
}
}

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -1,12 +1,13 @@
{
"name": "@semaphore-protocol/cli-template-hardhat",
"version": "3.2.3",
"version": "3.3.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.3",
"@semaphore-protocol/hardhat": "3.2.3",
"@semaphore-protocol/identity": "3.2.3",
"@semaphore-protocol/proof": "3.2.3",
"@semaphore-protocol/group": "3.3.0",
"@semaphore-protocol/hardhat": "3.3.0",
"@semaphore-protocol/identity": "3.3.0",
"@semaphore-protocol/proof": "3.3.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.3"
"@semaphore-protocol/contracts": "3.3.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"
}
}
}
}

View File

@@ -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)
})

View File

@@ -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
})

View 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)
})
})
})

View File

@@ -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)
})
})
})

View File

@@ -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"]
}

View File

@@ -1,7 +1,7 @@
{
"name": "@semaphore-protocol/cli",
"type": "module",
"version": "3.2.3",
"version": "3.3.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.3",
"@semaphore-protocol/data": "3.3.0",
"axios": "^1.3.2",
"boxen": "^7.0.1",
"chalk": "^5.1.2",

View File

@@ -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"]
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)

View File

@@ -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 {

View File

@@ -1,6 +1,6 @@
{
"name": "@semaphore-protocol/contracts",
"version": "3.2.3",
"version": "3.3.0",
"description": "Semaphore contracts to manage groups and broadcast anonymous signals.",
"license": "MIT",
"files": [

View File

@@ -0,0 +1,7 @@
{
"Pairing": "0xC0ae1a8D3505B2bE9DCe0e826abd722Afd13F1c9",
"SemaphoreVerifier": "0x346a936b19071b2f619200848B8ADbb938D72250",
"Poseidon": "0xb69aABB5D8d8e4920834761bD0C9DEEfa5D5502F",
"IncrementalBinaryTree": "0x9f44be9F69aF1e049dCeCDb2d9296f36C49Ceafb",
"Semaphore": "0xbE66454b0Fa9E6b3D53DC1b0f9D21978bb864531"
}

View File

@@ -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
}

View File

@@ -1,6 +1,6 @@
{
"name": "@semaphore-protocol/data",
"version": "3.2.3",
"version": "3.3.0",
"description": "A library to query Semaphore contracts.",
"license": "MIT",
"main": "dist/index.node.js",

View File

@@ -29,18 +29,22 @@ describe("SemaphoreEthers", () => {
semaphore = new SemaphoreEthers()
const semaphore1 = new SemaphoreEthers("arbitrum")
const semaphore2 = new SemaphoreEthers("matic")
const semaphore3 = new SemaphoreEthers("homestead", {
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("maticmum")
expect(semaphore3.network).toBe("homestead")
expect(semaphore3.options.startBlock).toBe(0)
expect(semaphore3.options.address).toContain("0x000000")
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", () => {

View File

@@ -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`)

View File

@@ -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:

View File

@@ -1,6 +1,6 @@
{
"name": "@semaphore-protocol/group",
"version": "3.2.3",
"version": "3.3.0",
"description": "A library to create and manage Semaphore groups.",
"license": "MIT",
"main": "dist/index.node.js",

View File

@@ -1,6 +1,6 @@
{
"name": "@semaphore-protocol/hardhat",
"version": "3.2.3",
"version": "3.3.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.3",
"@semaphore-protocol/contracts": "3.3.0",
"circomlibjs": "^0.0.8",
"ethers": "^5.7.1",
"hardhat-dependency-compiler": "^1.1.3"

21
packages/heyauthn/LICENSE Normal file
View 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
View 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>&nbsp;&nbsp;|&nbsp;&nbsp;</span>
<a href="https://github.com/semaphore-protocol/semaphore/blob/main/CODE_OF_CONDUCT.md">
🤝 Code of conduct
</a>
<span>&nbsp;&nbsp;|&nbsp;&nbsp;</span>
<a href="https://github.com/semaphore-protocol/semaphore/contribute">
🔎 Issues
</a>
<span>&nbsp;&nbsp;|&nbsp;&nbsp;</span>
<a href="https://semaphore.appliedzkp.org/discord">
🗣️ Chat &amp; 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

View File

@@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"baseUrl": ".",
"declarationDir": "dist/types"
},
"include": ["src"]
}

View File

@@ -0,0 +1,42 @@
{
"name": "@semaphore-protocol/heyauthn",
"version": "3.3.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.3.0",
"@simplewebauthn/browser": "7.2.0",
"@simplewebauthn/server": "7.2.0"
}
}

View 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" })
]
}

View 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)
})
})
})

View 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
}
}

View 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
}

View File

@@ -0,0 +1,4 @@
{
"extends": "../../tsconfig.json",
"include": ["src", "rollup.config.ts"]
}

View File

@@ -1,6 +1,6 @@
{
"name": "@semaphore-protocol/identity",
"version": "3.2.3",
"version": "3.3.0",
"description": "A library to create Semaphore identities.",
"license": "MIT",
"main": "dist/index.node.js",

View File

@@ -1,6 +1,6 @@
{
"name": "@semaphore-protocol/proof",
"version": "3.2.3",
"version": "3.3.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.3",
"@semaphore-protocol/identity": "3.2.3"
"@semaphore-protocol/group": "3.3.0",
"@semaphore-protocol/identity": "3.3.0"
},
"dependencies": {
"@ethersproject/bignumber": "^5.5.0",

View File

@@ -1 +1 @@
5d109b5b737dadc4046717430e8fd16c727749b9
db9ac44677af043f133407f3ec845b045c7ad6fa