@semaphore-protocol/proof 0.1.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,123 @@
1
+ import { Identity } from "@semaphore-protocol/identity"
2
+ import download from "download"
3
+ import { getCurveFromName } from "ffjavascript"
4
+ import fs from "fs"
5
+ import createMerkleProof from "./createMerkleProof"
6
+ import createMerkleTree from "./createMerkleTree"
7
+ import generateNullifierHash from "./generateNullifierHash"
8
+ import generateProof from "./generateProof"
9
+ import generateSignalHash from "./generateSignalHash"
10
+ import packToSolidityProof from "./packToSolidityProof"
11
+ import { FullProof } from "./types"
12
+ import verifyProof from "./verifyProof"
13
+
14
+ describe("Proof", () => {
15
+ const treeDepth = 20
16
+ const externalNullifier = "1"
17
+ const signal = "0x111"
18
+
19
+ const snarkArtifactsPath = "./packages/proof/snark-artifacts"
20
+ const snarkArtifactsUrl = `http://www.trusted-setup-pse.org/semaphore/${treeDepth}`
21
+
22
+ let identity: Identity
23
+ let fullProof: FullProof
24
+ let curve: any
25
+
26
+ beforeAll(async () => {
27
+ curve = await getCurveFromName("bn128")
28
+
29
+ if (!fs.existsSync(snarkArtifactsPath)) {
30
+ fs.mkdirSync(snarkArtifactsPath)
31
+ }
32
+
33
+ if (!fs.existsSync(`${snarkArtifactsPath}/semaphore.zkey`)) {
34
+ await download(`${snarkArtifactsUrl}/semaphore.wasm`, snarkArtifactsPath)
35
+ await download(`${snarkArtifactsUrl}/semaphore.zkey`, snarkArtifactsPath)
36
+ await download(`${snarkArtifactsUrl}/semaphore.json`, snarkArtifactsPath)
37
+ }
38
+ })
39
+
40
+ afterAll(async () => {
41
+ await curve.terminate()
42
+ })
43
+
44
+ describe("# createMerkleTree", () => {
45
+ it("Should not generate a Merkle tree with a zero value leaf", async () => {
46
+ const fun = () => createMerkleTree(treeDepth, BigInt(0), [BigInt(0), BigInt(1)])
47
+
48
+ expect(fun).toThrow("Leaves cannot have zero values")
49
+ })
50
+
51
+ it("Should generate a Merkle tree", async () => {
52
+ const merkleTree = createMerkleTree(treeDepth, BigInt(0), [BigInt(1), BigInt(2)])
53
+
54
+ expect(merkleTree.leaves).toHaveLength(2)
55
+ })
56
+ })
57
+
58
+ describe("# createMerkleProof", () => {
59
+ it("Should not generate a Merkle proof of a non-existing leaf", async () => {
60
+ const fun = () => createMerkleProof(treeDepth, BigInt(0), [BigInt(1), BigInt(2)], BigInt(3))
61
+
62
+ expect(fun).toThrow("The leaf does not exist")
63
+ })
64
+
65
+ it("Should generate a Merkle proof", async () => {
66
+ const merkleProof = createMerkleProof(treeDepth, BigInt(0), [BigInt(1), BigInt(2)], BigInt(2))
67
+
68
+ expect(merkleProof.leaf).toBe(BigInt(2))
69
+ })
70
+ })
71
+
72
+ describe("# generateProof", () => {
73
+ it("Should generate a Semaphore proof", async () => {
74
+ identity = new Identity()
75
+ const identityCommitment = identity.generateCommitment()
76
+ const leaves = [BigInt(3), BigInt(2), identityCommitment, BigInt(4)]
77
+ const merkleProof = createMerkleProof(treeDepth, BigInt(0), leaves, identityCommitment)
78
+
79
+ fullProof = await generateProof(identity, merkleProof, externalNullifier, signal, {
80
+ wasmFilePath: `${snarkArtifactsPath}/semaphore.wasm`,
81
+ zkeyFilePath: `${snarkArtifactsPath}/semaphore.zkey`
82
+ })
83
+
84
+ expect(typeof fullProof).toBe("object")
85
+ expect(fullProof.publicSignals.externalNullifier).toBe(externalNullifier)
86
+ expect(fullProof.publicSignals.merkleRoot).toBe(merkleProof.root.toString())
87
+ }, 20000)
88
+ })
89
+
90
+ describe("# generateSignalHash", () => {
91
+ it("Should generate a valid signal hash", async () => {
92
+ const signalHash = generateSignalHash(signal)
93
+
94
+ expect(signalHash.toString()).toBe(fullProof.publicSignals.signalHash)
95
+ })
96
+ })
97
+
98
+ describe("# generateNullifierHash", () => {
99
+ it("Should generate a valid nullifier hash", async () => {
100
+ const nullifierHash = generateNullifierHash(externalNullifier, identity.getNullifier())
101
+
102
+ expect(nullifierHash.toString()).toBe(fullProof.publicSignals.nullifierHash)
103
+ })
104
+ })
105
+
106
+ describe("# packToSolidityProof", () => {
107
+ it("Should return a Solidity proof", async () => {
108
+ const solidityProof = packToSolidityProof(fullProof.proof)
109
+
110
+ expect(solidityProof).toHaveLength(8)
111
+ })
112
+ })
113
+
114
+ describe("# verifyProof", () => {
115
+ it("Should generate and verify a Semaphore proof", async () => {
116
+ const verificationKey = JSON.parse(fs.readFileSync(`${snarkArtifactsPath}/semaphore.json`, "utf-8"))
117
+
118
+ const response = await verifyProof(verificationKey, fullProof)
119
+
120
+ expect(response).toBe(true)
121
+ })
122
+ })
123
+ })
package/src/index.ts CHANGED
@@ -1,6 +1,20 @@
1
1
  import { MerkleProof } from "@zk-kit/incremental-merkle-tree"
2
- import Semaphore from "./semaphore"
3
- import { generateMerkleProof, generateMerkleTree, genExternalNullifier } from "./utils"
2
+ import createMerkleTree from "./createMerkleTree"
3
+ import createMerkleProof from "./createMerkleProof"
4
+ import generateNullifierHash from "./generateNullifierHash"
5
+ import generateProof from "./generateProof"
6
+ import verifyProof from "./verifyProof"
7
+ import generateSignalHash from "./generateSignalHash"
8
+ import packToSolidityProof from "./packToSolidityProof"
4
9
 
5
- export { Semaphore, generateMerkleProof, generateMerkleTree, genExternalNullifier, MerkleProof }
10
+ export {
11
+ MerkleProof,
12
+ createMerkleTree,
13
+ createMerkleProof,
14
+ generateNullifierHash,
15
+ generateProof,
16
+ verifyProof,
17
+ generateSignalHash,
18
+ packToSolidityProof
19
+ }
6
20
  export * from "./types"
@@ -0,0 +1,19 @@
1
+ import { Proof, SolidityProof } from "./types"
2
+
3
+ /**
4
+ * Makes a proof compatible with the Verifier.sol method inputs.
5
+ * @param proof The proof generated with SnarkJS.
6
+ * @returns The Solidity compatible proof.
7
+ */
8
+ export default function packToSolidityProof(proof: Proof): SolidityProof {
9
+ return [
10
+ proof.pi_a[0],
11
+ proof.pi_a[1],
12
+ proof.pi_b[0][1],
13
+ proof.pi_b[0][0],
14
+ proof.pi_b[1][1],
15
+ proof.pi_b[1][0],
16
+ proof.pi_c[0],
17
+ proof.pi_c[1]
18
+ ]
19
+ }
@@ -1,41 +1,37 @@
1
- export type StrBigInt = string | bigint
1
+ export type BigNumberish = string | bigint
2
+
3
+ export type SnarkArtifacts = {
4
+ wasmFilePath: string
5
+ zkeyFilePath: string
6
+ }
2
7
 
3
8
  export type Proof = {
4
- pi_a: StrBigInt[]
5
- pi_b: StrBigInt[][]
6
- pi_c: StrBigInt[]
9
+ pi_a: BigNumberish[]
10
+ pi_b: BigNumberish[][]
11
+ pi_c: BigNumberish[]
7
12
  protocol: string
8
13
  curve: string
9
14
  }
10
15
 
11
- export type SemaphoreFullProof = {
16
+ export type FullProof = {
12
17
  proof: Proof
13
- publicSignals: SemaphorePublicSignals
18
+ publicSignals: PublicSignals
14
19
  }
15
20
 
16
- export type SemaphorePublicSignals = {
17
- merkleRoot: StrBigInt
18
- nullifierHash: StrBigInt
19
- signalHash: StrBigInt
20
- externalNullifier: StrBigInt
21
+ export type PublicSignals = {
22
+ merkleRoot: BigNumberish
23
+ nullifierHash: BigNumberish
24
+ signalHash: BigNumberish
25
+ externalNullifier: BigNumberish
21
26
  }
22
27
 
23
- export type SemaphoreSolidityProof = [
24
- StrBigInt,
25
- StrBigInt,
26
- StrBigInt,
27
- StrBigInt,
28
- StrBigInt,
29
- StrBigInt,
30
- StrBigInt,
31
- StrBigInt
28
+ export type SolidityProof = [
29
+ BigNumberish,
30
+ BigNumberish,
31
+ BigNumberish,
32
+ BigNumberish,
33
+ BigNumberish,
34
+ BigNumberish,
35
+ BigNumberish,
36
+ BigNumberish
32
37
  ]
33
-
34
- export type SemaphoreWitness = {
35
- identityNullifier: StrBigInt
36
- identityTrapdoor: StrBigInt
37
- treeSiblings: StrBigInt[]
38
- treePathIndices: number[]
39
- externalNullifier: StrBigInt
40
- signalHash: StrBigInt
41
- }
@@ -0,0 +1,21 @@
1
+ import { groth16 } from "snarkjs"
2
+ import { FullProof } from "./types"
3
+
4
+ /**
5
+ * Verifies a SnarkJS proof.
6
+ * @param verificationKey The zero-knowledge verification key.
7
+ * @param fullProof The SnarkJS full proof.
8
+ * @returns True if the proof is valid, false otherwise.
9
+ */
10
+ export default function verifyProof(verificationKey: any, { proof, publicSignals }: FullProof): Promise<boolean> {
11
+ return groth16.verify(
12
+ verificationKey,
13
+ [
14
+ publicSignals.merkleRoot,
15
+ publicSignals.nullifierHash,
16
+ publicSignals.signalHash,
17
+ publicSignals.externalNullifier
18
+ ],
19
+ proof
20
+ )
21
+ }
@@ -1,48 +0,0 @@
1
- import { MerkleProof } from "@zk-kit/incremental-merkle-tree";
2
- import { Proof, SemaphoreFullProof, SemaphoreSolidityProof, SemaphoreWitness, StrBigInt } from "./types";
3
- export default class Semaphore {
4
- /**
5
- * Generates a SnarkJS full proof with Groth16.
6
- * @param witness The parameters for creating the proof.
7
- * @param wasmFilePath The WASM file path.
8
- * @param finalZkeyPath The ZKey file path.
9
- * @returns The full SnarkJS proof.
10
- */
11
- static genProof(witness: any, wasmFilePath: string, finalZkeyPath: string): Promise<SemaphoreFullProof>;
12
- /**
13
- * Verifies a zero-knowledge SnarkJS proof.
14
- * @param verificationKey The zero-knowledge verification key.
15
- * @param fullProof The SnarkJS full proof.
16
- * @returns True if the proof is valid, false otherwise.
17
- */
18
- static verifyProof(verificationKey: string, { proof, publicSignals }: SemaphoreFullProof): Promise<boolean>;
19
- /**
20
- * Creates a Semaphore witness for the Semaphore ZK proof.
21
- * @param identityTrapdoor The identity trapdoor.
22
- * @param identityNullifier The identity nullifier.
23
- * @param merkleProof The Merkle proof that identity exists in Semaphore tree.
24
- * @param externalNullifier The topic on which vote should be broadcasted.
25
- * @param signal The signal that should be broadcasted.
26
- * @returns The Semaphore witness.
27
- */
28
- static genWitness(identityTrapdoor: StrBigInt, identityNullifier: StrBigInt, merkleProof: MerkleProof, externalNullifier: StrBigInt, signal: string): SemaphoreWitness;
29
- /**
30
- * Generates a nullifier by hashing the external and the identity nullifiers.
31
- * @param externalNullifier The external nullifier.
32
- * @param identityNullifier The identity nullifier.
33
- * @returns The nullifier hash.
34
- */
35
- static genNullifierHash(externalNullifier: StrBigInt, identityNullifier: StrBigInt): bigint;
36
- /**
37
- * Hashes a signal string with Keccak256.
38
- * @param signal The Semaphore signal.
39
- * @returns The signal hash.
40
- */
41
- static genSignalHash(signal: string): bigint;
42
- /**
43
- * Converts a full proof in a proof compatible with the Verifier.sol method inputs.
44
- * @param proof The proof generated with SnarkJS.
45
- * @returns The Solidity compatible proof.
46
- */
47
- static packToSolidityProof(proof: Proof): SemaphoreSolidityProof;
48
- }
@@ -1,21 +0,0 @@
1
- import { IncrementalMerkleTree, MerkleProof } from "@zk-kit/incremental-merkle-tree";
2
- import { StrBigInt } from "./types";
3
- export declare const SNARK_FIELD_SIZE: bigint;
4
- export declare function genExternalNullifier(plaintext: string): string;
5
- /**
6
- * Creates a Merkle tree.
7
- * @param depth The depth of the tree.
8
- * @param zeroValue The zero value of the tree.
9
- * @param leaves The list of the leaves of the tree.
10
- * @returns The Merkle tree.
11
- */
12
- export declare function generateMerkleTree(depth: number, zeroValue: StrBigInt, leaves: StrBigInt[]): IncrementalMerkleTree;
13
- /**
14
- * Creates a Merkle proof.
15
- * @param depth The depth of the tree.
16
- * @param zeroValue The zero value of the tree.
17
- * @param leaves The list of the leaves of the tree.
18
- * @param leaf The leaf for which Merkle proof should be created.
19
- * @returns The Merkle proof.
20
- */
21
- export declare function generateMerkleProof(depth: number, zeroValue: StrBigInt, leaves: StrBigInt[], leaf: StrBigInt): MerkleProof;
@@ -1,72 +0,0 @@
1
- import { Identity } from "@semaphore-protocol/identity"
2
- import { getCurveFromName } from "ffjavascript"
3
- import fs from "fs"
4
- import path from "path"
5
- import Semaphore from "./semaphore"
6
- import { generateMerkleProof, genExternalNullifier } from "./utils"
7
-
8
- describe("Semaphore", () => {
9
- const assets = "./packages/proof/assets"
10
-
11
- let curve: any
12
-
13
- beforeAll(async () => {
14
- curve = await getCurveFromName("bn128")
15
- })
16
-
17
- afterAll(async () => {
18
- await curve.terminate()
19
- })
20
-
21
- describe("Generate and verify proof", () => {
22
- it("Should generate a Semaphore witness", async () => {
23
- const identity = new Identity()
24
- const identityCommitment = identity.genIdentityCommitment()
25
- const externalNullifier = genExternalNullifier("voting_1")
26
- const leaves = [BigInt(3), BigInt(2), identityCommitment, BigInt(4)]
27
- const signal = "0x111"
28
-
29
- const merkleProof = generateMerkleProof(20, BigInt(0), leaves, identityCommitment)
30
-
31
- const witness = Semaphore.genWitness(
32
- identity.getTrapdoor(),
33
- identity.getNullifier(),
34
- merkleProof,
35
- externalNullifier,
36
- signal
37
- )
38
-
39
- expect(typeof witness).toBe("object")
40
- })
41
-
42
- // eslint-disable-next-line jest/no-disabled-tests
43
- it.skip("Should generate and verify Semaphore proof", async () => {
44
- const identity = new Identity()
45
- const identityCommitment = identity.genIdentityCommitment()
46
- const externalNullifier = genExternalNullifier("voting_1")
47
- const leaves = [BigInt(3), BigInt(2), identityCommitment, BigInt(4)]
48
- const signal = "0x111"
49
-
50
- const merkleProof = generateMerkleProof(20, BigInt(0), leaves, identityCommitment)
51
-
52
- const witness = Semaphore.genWitness(
53
- identity.getTrapdoor(),
54
- identity.getNullifier(),
55
- merkleProof,
56
- externalNullifier,
57
- signal
58
- )
59
-
60
- const wasmFilePath = path.join(assets, "semaphore", "semaphore.wasm")
61
- const zkeyFilePath = path.join(assets, "semaphore", "semaphore.zkey")
62
- const fullProof = await Semaphore.genProof(witness, wasmFilePath, zkeyFilePath)
63
-
64
- const vkeyPath = path.join(assets, "semaphore", "verification_key.json")
65
- const vKey = JSON.parse(fs.readFileSync(vkeyPath, "utf-8"))
66
-
67
- const response = await Semaphore.verifyProof(vKey, fullProof)
68
-
69
- expect(response).toBe(true)
70
- }, 30000)
71
- })
72
- })
package/src/semaphore.ts DELETED
@@ -1,118 +0,0 @@
1
- import { keccak256 } from "@ethersproject/solidity"
2
- import { formatBytes32String } from "@ethersproject/strings"
3
- import { MerkleProof } from "@zk-kit/incremental-merkle-tree"
4
- import { poseidon } from "circomlibjs"
5
- import { groth16 } from "snarkjs"
6
- import { Proof, SemaphoreFullProof, SemaphoreSolidityProof, SemaphoreWitness, StrBigInt } from "./types"
7
-
8
- export default class Semaphore {
9
- /**
10
- * Generates a SnarkJS full proof with Groth16.
11
- * @param witness The parameters for creating the proof.
12
- * @param wasmFilePath The WASM file path.
13
- * @param finalZkeyPath The ZKey file path.
14
- * @returns The full SnarkJS proof.
15
- */
16
- /* istanbul ignore next */
17
- public static async genProof(
18
- witness: any,
19
- wasmFilePath: string,
20
- finalZkeyPath: string
21
- ): Promise<SemaphoreFullProof> {
22
- const { proof, publicSignals } = await groth16.fullProve(witness, wasmFilePath, finalZkeyPath, null)
23
-
24
- return {
25
- proof,
26
- publicSignals: {
27
- merkleRoot: publicSignals[0],
28
- nullifierHash: publicSignals[1],
29
- signalHash: publicSignals[2],
30
- externalNullifier: publicSignals[3]
31
- }
32
- }
33
- }
34
-
35
- /**
36
- * Verifies a zero-knowledge SnarkJS proof.
37
- * @param verificationKey The zero-knowledge verification key.
38
- * @param fullProof The SnarkJS full proof.
39
- * @returns True if the proof is valid, false otherwise.
40
- */
41
- /* istanbul ignore next */
42
- public static verifyProof(verificationKey: string, { proof, publicSignals }: SemaphoreFullProof): Promise<boolean> {
43
- return groth16.verify(
44
- verificationKey,
45
- [
46
- publicSignals.merkleRoot,
47
- publicSignals.nullifierHash,
48
- publicSignals.signalHash,
49
- publicSignals.externalNullifier
50
- ],
51
- proof
52
- )
53
- }
54
-
55
- /**
56
- * Creates a Semaphore witness for the Semaphore ZK proof.
57
- * @param identityTrapdoor The identity trapdoor.
58
- * @param identityNullifier The identity nullifier.
59
- * @param merkleProof The Merkle proof that identity exists in Semaphore tree.
60
- * @param externalNullifier The topic on which vote should be broadcasted.
61
- * @param signal The signal that should be broadcasted.
62
- * @returns The Semaphore witness.
63
- */
64
- public static genWitness(
65
- identityTrapdoor: StrBigInt,
66
- identityNullifier: StrBigInt,
67
- merkleProof: MerkleProof,
68
- externalNullifier: StrBigInt,
69
- signal: string
70
- ): SemaphoreWitness {
71
- return {
72
- identityNullifier,
73
- identityTrapdoor,
74
- treePathIndices: merkleProof.pathIndices,
75
- treeSiblings: merkleProof.siblings,
76
- externalNullifier,
77
- signalHash: Semaphore.genSignalHash(signal)
78
- }
79
- }
80
-
81
- /**
82
- * Generates a nullifier by hashing the external and the identity nullifiers.
83
- * @param externalNullifier The external nullifier.
84
- * @param identityNullifier The identity nullifier.
85
- * @returns The nullifier hash.
86
- */
87
- public static genNullifierHash(externalNullifier: StrBigInt, identityNullifier: StrBigInt): bigint {
88
- return poseidon([BigInt(externalNullifier), BigInt(identityNullifier)])
89
- }
90
-
91
- /**
92
- * Hashes a signal string with Keccak256.
93
- * @param signal The Semaphore signal.
94
- * @returns The signal hash.
95
- */
96
- public static genSignalHash(signal: string): bigint {
97
- return BigInt(keccak256(["bytes32"], [formatBytes32String(signal)])) >> BigInt(8)
98
- }
99
-
100
- /**
101
- * Converts a full proof in a proof compatible with the Verifier.sol method inputs.
102
- * @param proof The proof generated with SnarkJS.
103
- * @returns The Solidity compatible proof.
104
- */
105
- /* istanbul ignore next */
106
- public static packToSolidityProof(proof: Proof): SemaphoreSolidityProof {
107
- return [
108
- proof.pi_a[0],
109
- proof.pi_a[1],
110
- proof.pi_b[0][1],
111
- proof.pi_b[0][0],
112
- proof.pi_b[1][1],
113
- proof.pi_b[1][0],
114
- proof.pi_c[0],
115
- proof.pi_c[1]
116
- ]
117
- }
118
- }
package/src/utils.ts DELETED
@@ -1,65 +0,0 @@
1
- import { keccak256 } from "@ethersproject/solidity"
2
- import { IncrementalMerkleTree, MerkleProof } from "@zk-kit/incremental-merkle-tree"
3
- import { poseidon } from "circomlibjs"
4
- import { StrBigInt } from "./types"
5
-
6
- export const SNARK_FIELD_SIZE = BigInt("21888242871839275222246405745257275088548364400416034343698204186575808495617")
7
-
8
- export function genExternalNullifier(plaintext: string): string {
9
- const hashed = keccak256(["string"], [plaintext])
10
- const hexStr = `0x${hashed.slice(8)}`
11
- const len = 32 * 2
12
- const h = hexStr.slice(2, len + 2)
13
-
14
- return `0x${h.padStart(len, "0")}`
15
- }
16
-
17
- /**
18
- * Creates a Merkle tree.
19
- * @param depth The depth of the tree.
20
- * @param zeroValue The zero value of the tree.
21
- * @param leaves The list of the leaves of the tree.
22
- * @returns The Merkle tree.
23
- */
24
- export function generateMerkleTree(depth: number, zeroValue: StrBigInt, leaves: StrBigInt[]): IncrementalMerkleTree {
25
- const tree = new IncrementalMerkleTree(poseidon, depth, zeroValue, 2)
26
-
27
- for (const leaf of leaves) {
28
- tree.insert(BigInt(leaf))
29
- }
30
-
31
- return tree
32
- }
33
-
34
- /**
35
- * Creates a Merkle proof.
36
- * @param depth The depth of the tree.
37
- * @param zeroValue The zero value of the tree.
38
- * @param leaves The list of the leaves of the tree.
39
- * @param leaf The leaf for which Merkle proof should be created.
40
- * @returns The Merkle proof.
41
- */
42
- export function generateMerkleProof(
43
- depth: number,
44
- zeroValue: StrBigInt,
45
- leaves: StrBigInt[],
46
- leaf: StrBigInt
47
- ): MerkleProof {
48
- if (leaf === zeroValue) {
49
- throw new Error("Can't generate a proof for a zero leaf")
50
- }
51
-
52
- const tree = generateMerkleTree(depth, zeroValue, leaves)
53
-
54
- const leafIndex = tree.leaves.indexOf(BigInt(leaf))
55
-
56
- if (leafIndex === -1) {
57
- throw new Error("The leaf does not exist")
58
- }
59
-
60
- const merkleProof = tree.createProof(leafIndex)
61
-
62
- merkleProof.siblings = merkleProof.siblings.map((s) => s[0])
63
-
64
- return merkleProof
65
- }