Skip to content

Commit

Permalink
feat(proof): update proof package with new circuit
Browse files Browse the repository at this point in the history
  • Loading branch information
cedoor committed Jan 12, 2024
1 parent f80bb04 commit bc5b294
Show file tree
Hide file tree
Showing 20 changed files with 300 additions and 952 deletions.
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
"build:libraries": "yarn workspaces foreach -t --no-private run build",
"build:subgraph": "yarn workspace semaphore-subgraph codegen goerli && yarn workspace semaphore-subgraph build",
"compile:contracts": "yarn workspace semaphore-contracts compile",
"download:snark-artifacts": "rimraf snark-artifacts && ts-node scripts/download-snark-artifacts.ts",
"remove:template-files": "ts-node scripts/remove-template-files.ts",
"test": "yarn test:libraries && yarn test:contracts && yarn test:circuits && yarn test:subgraph",
"test:libraries": "jest --coverage",
Expand All @@ -26,7 +25,7 @@
"version:release": "changelogithub",
"commit": "cz",
"precommit": "lint-staged",
"postinstall": "yarn download:snark-artifacts && husky install"
"postinstall": "husky install"
},
"keywords": [
"ethereum",
Expand Down
1 change: 1 addition & 0 deletions packages/circuits/circuits.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"semaphore": {
"file": "semaphore",
"template": "Semaphore",
"pubs": ["message", "scope"],
"params": [10]
}
}
4 changes: 2 additions & 2 deletions packages/group/src/group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@ export default class Group {
* @param index Index of the proof's member.
* @returns Proof object.
*/
generateMerkleProof(index: number): MerkleProof {
const { leaf, root, siblings } = this.leanIMT.generateProof(index)
generateMerkleProof(_index: number): MerkleProof {
const { index, leaf, root, siblings } = this.leanIMT.generateProof(_index)

return {
index,
Expand Down
5 changes: 4 additions & 1 deletion packages/proof/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
"@rollup/plugin-commonjs": "^24.1.0",
"@rollup/plugin-json": "^5.0.1",
"@rollup/plugin-node-resolve": "^15.0.2",
"@types/download": "^8.0.5",
"@types/tmp": "^0.2.6",
"poseidon-lite": "^0.2.0",
"rimraf": "^5.0.5",
"rollup": "^4.0.2",
Expand All @@ -49,6 +51,7 @@
"@ethersproject/keccak256": "^5.7.0",
"@ethersproject/strings": "^5.5.0",
"@zk-kit/groth16": "0.3.0",
"@zk-kit/incremental-merkle-tree": "0.4.3"
"download": "^8.0.0",
"tmp": "^0.2.1"
}
}
16 changes: 0 additions & 16 deletions packages/proof/src/calculateNullifierHash.ts

This file was deleted.

75 changes: 75 additions & 0 deletions packages/proof/src/generate-proof.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { BigNumber } from "@ethersproject/bignumber"
import { BytesLike, Hexable } from "@ethersproject/bytes"
import { Group } from "@semaphore-protocol/group"
import type { Identity } from "@semaphore-protocol/identity"
import { prove } from "@zk-kit/groth16"
import type { NumericString } from "snarkjs"
import getSnarkArtifacts from "./get-snark-artifacts.node"
import hash from "./hash"
import packProof from "./pack-proof"
import { SemaphoreProof, SnarkArtifacts } from "./types"

/**
* Generates a Semaphore proof.
* @param identity The Semaphore identity.
* @param group The Semaphore group or its Merkle proof.
* @param scope The external nullifier.
* @param message The Semaphore signal.
* @param treeDepth The depth of the tree with which the circuit was compiled.
* @param snarkArtifacts The SNARK artifacts.
* @returns The Semaphore proof ready to be verified.
*/
export default async function generateProof(
identity: Identity,
group: Group,
scope: BytesLike | Hexable | number | bigint,
message: BytesLike | Hexable | number | bigint,
treeDepth?: number,
snarkArtifacts?: SnarkArtifacts
): Promise<SemaphoreProof> {
const leafIndex = group.indexOf(identity.commitment)
const merkeProof = group.generateMerkleProof(leafIndex)
const merkleProofLength = merkeProof.siblings.length

treeDepth ??= merkleProofLength

// If the Snark artifacts are not defined they will be automatically downloaded.
/* istanbul ignore next */
if (!snarkArtifacts) {
snarkArtifacts = await getSnarkArtifacts(treeDepth)
}

// The index must be converted to a list of indices, 1 for each tree level.
// The missing siblings can be set to 0, as they won't be used in the circuit.
const treeIndices = []
const treeSiblings = merkeProof.siblings

for (let i = 0; i < treeDepth; i += 1) {
treeIndices.push((merkeProof.index >> i) & 1)

if (treeSiblings[i] === undefined) {
treeSiblings[i] = "0"
}
}

const { proof, publicSignals } = await prove(
{
privateKey: identity.secretScalar,
treeDepth: merkleProofLength,
treeIndices,
treeSiblings,
scope: hash(scope),
message: hash(message)
},
snarkArtifacts.wasmFilePath,
snarkArtifacts.zkeyFilePath
)

return {
treeRoot: publicSignals[0],
nullifier: publicSignals[1],
scope: BigNumber.from(scope).toString() as NumericString,
message: BigNumber.from(message).toString() as NumericString,
proof: packProof(proof)
}
}
69 changes: 0 additions & 69 deletions packages/proof/src/generateProof.ts

This file was deleted.

9 changes: 9 additions & 0 deletions packages/proof/src/get-snark-artifacts.browser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/* istanbul ignore file */
import { SnarkArtifacts } from "./types"

export default async function getSnarkArtifacts(treeDepth: number): Promise<SnarkArtifacts> {
return {
wasmFilePath: `https://semaphore.cedoor.dev/artifacts/${treeDepth}/semaphore.wasm`,
zkeyFilePath: `https://semaphore.cedoor.dev/artifacts/${treeDepth}/semaphore.zkey`
}
}
22 changes: 22 additions & 0 deletions packages/proof/src/get-snark-artifacts.node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/* istanbul ignore file */
import download from "download"
import fs from "fs"
import tmp from "tmp"
import { SnarkArtifacts } from "./types"

export default async function getSnarkArtifacts(treeDepth: number): Promise<SnarkArtifacts> {
const tmpDir = "semaphore-proof"
const tmpPath = `${tmp.tmpdir}/${tmpDir}`

if (!fs.existsSync(tmpPath)) {
tmp.dirSync({ name: tmpDir })

await download(`https://semaphore.cedoor.dev/artifacts/${treeDepth}/semaphore.wasm`, tmpPath)
await download(`https://semaphore.cedoor.dev/artifacts/${treeDepth}/semaphore.zkey`, tmpPath)
}

return {
wasmFilePath: `${tmpPath}/semaphore.wasm`,
zkeyFilePath: `${tmpPath}/semaphore.zkey`
}
}
93 changes: 23 additions & 70 deletions packages/proof/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,20 @@ import { formatBytes32String } from "@ethersproject/strings"
import { Group } from "@semaphore-protocol/group"
import { Identity } from "@semaphore-protocol/identity"
import { getCurveFromName } from "ffjavascript"
import calculateNullifierHash from "./calculateNullifierHash"
import generateProof from "./generateProof"
import generateProof from "./generate-proof"
import hash from "./hash"
import packProof from "./packProof"
import packProof from "./pack-proof"
import { SemaphoreProof } from "./types"
import unpackProof from "./unpackProof"
import verifyProof from "./verifyProof"
import unpackProof from "./unpack-proof"
import verifyProof from "./verify-proof"

describe("Proof", () => {
const treeDepth = Number(process.env.TREE_DEPTH) || 20
const treeDepth = 10

const externalNullifier = formatBytes32String("Topic")
const signal = formatBytes32String("Hello world")
const scope = formatBytes32String("Scope")
const message = formatBytes32String("Hello world")

const wasmFilePath = `./snark-artifacts/${treeDepth}/semaphore.wasm`
const zkeyFilePath = `./snark-artifacts/${treeDepth}/semaphore.zkey`

const identity = new Identity()
const identity = new Identity(42)

let fullProof: SemaphoreProof
let curve: any
Expand All @@ -34,77 +30,42 @@ describe("Proof", () => {

describe("# generateProof", () => {
it("Should not generate Semaphore proofs if the identity is not part of the group", async () => {
const group = new Group(treeDepth, 20, [BigInt(1), BigInt(2)])

const fun = () =>
generateProof(identity, group, externalNullifier, signal, {
wasmFilePath,
zkeyFilePath
})

await expect(fun).rejects.toThrow("The identity is not part of the group")
})

it("Should not generate a Semaphore proof with default snark artifacts with Node.js", async () => {
const group = new Group(treeDepth, 20, [BigInt(1), BigInt(2), identity.commitment])
const group = new Group([BigInt(1), BigInt(2)])

const fun = () => generateProof(identity, group, externalNullifier, signal)
const fun = () => generateProof(identity, group, scope, message, treeDepth)

await expect(fun).rejects.toThrow("ENOENT: no such file or directory")
await expect(fun).rejects.toThrow("does not exist")
})

it("Should generate a Semaphore proof passing a group as parameter", async () => {
const group = new Group(treeDepth, 20, [BigInt(1), BigInt(2), identity.commitment])

fullProof = await generateProof(identity, group, externalNullifier, signal, {
wasmFilePath,
zkeyFilePath
})

expect(typeof fullProof).toBe("object")
expect(fullProof.merkleTreeRoot).toBe(group.root.toString())
}, 20000)

it("Should generate a Semaphore proof passing a Merkle proof as parameter", async () => {
const group = new Group(treeDepth, 20, [BigInt(1), BigInt(2), identity.commitment])
it("Should generate a Semaphore proof", async () => {
const group = new Group([BigInt(1), BigInt(2), identity.commitment])

fullProof = await generateProof(identity, group.generateMerkleProof(2), externalNullifier, signal, {
wasmFilePath,
zkeyFilePath
})
fullProof = await generateProof(identity, group, scope, message, treeDepth)

expect(typeof fullProof).toBe("object")
expect(fullProof.merkleTreeRoot).toBe(group.root.toString())
}, 20000)
expect(fullProof.treeRoot).toBe(group.root)
})
})

describe("# verifyProof", () => {
it("Should not verify a proof if the tree depth is wrong", () => {
const fun = () => verifyProof(fullProof, 3)

expect(fun).toThrow("The tree depth must be a number between 16 and 32")
})

it("Should verify a Semaphore proof", async () => {
const response = await verifyProof(fullProof, treeDepth)
const response = await verifyProof(fullProof)

expect(response).toBe(true)
})
})

describe("# hash", () => {
it("Should hash the signal value correctly", async () => {
const signalHash = hash(signal)
it("Should hash the message correctly", async () => {
const messageHash = hash(message)

expect(signalHash).toBe("8665846418922331996225934941481656421248110469944536651334918563951783029")
expect(messageHash).toBe("8665846418922331996225934941481656421248110469944536651334918563951783029")
})

it("Should hash the external nullifier value correctly", async () => {
const externalNullifierHash = hash(externalNullifier)
it("Should hash the scope correctly", async () => {
const scopeHash = hash(scope)

expect(externalNullifierHash).toBe(
"244178201824278269437519042830883072613014992408751798420801126401127326826"
)
expect(scopeHash).toBe("170164770795872309789133717676167925425155944778337387941930839678899666300")
})

it("Should hash a number", async () => {
Expand All @@ -128,14 +89,6 @@ describe("Proof", () => {
})
})

describe("# calculateNullifierHash", () => {
it("Should calculate the nullifier hash correctly", async () => {
const nullifierHash = calculateNullifierHash(identity.nullifier, externalNullifier)

expect(fullProof.nullifierHash).toBe(nullifierHash.toString())
})
})

describe("# packProof/unpackProof", () => {
it("Should return a packed proof", async () => {
const originalProof = unpackProof(fullProof.proof)
Expand Down
Loading

0 comments on commit bc5b294

Please sign in to comment.