diff --git a/packages/protocols/src/index.ts b/packages/protocols/src/index.ts index 0203f9f..a57ee5a 100644 --- a/packages/protocols/src/index.ts +++ b/packages/protocols/src/index.ts @@ -1,17 +1,7 @@ import { MerkleProof } from "@zk-kit/incremental-merkle-tree" import RLN from "./rln" import Semaphore from "./semaphore" -import { FullProof, SolidityProof } from "./types" import { generateMerkleProof, generateMerkleTree, genExternalNullifier, genSignalHash } from "./utils" -export { - Semaphore, - RLN, - generateMerkleProof, - generateMerkleTree, - genExternalNullifier, - genSignalHash, - MerkleProof, - FullProof, - SolidityProof -} +export { Semaphore, RLN, generateMerkleProof, generateMerkleTree, genExternalNullifier, genSignalHash, MerkleProof } +export * from "./types" diff --git a/packages/protocols/src/rln.ts b/packages/protocols/src/rln.ts index 1639660..1cbde55 100644 --- a/packages/protocols/src/rln.ts +++ b/packages/protocols/src/rln.ts @@ -1,16 +1,10 @@ import { MerkleProof } from "@zk-kit/incremental-merkle-tree" import { poseidon } from "circomlibjs" import { groth16 } from "snarkjs" -import { FullProof, RLNPublicSignals, StrBigInt } from "./types" +import { RLNFullProof, StrBigInt } from "./types" import { Fq, genSignalHash } from "./utils" -import ZkProtocol from "./zk-protocol" - -export default class RLN extends ZkProtocol { - /** - * The number of public signals that should be returned by snarkjs when generating a proof. - */ - private static PUBLIC_SIGNALS_COUNT: number = 6 +export default class RLN { /** * Generates a SnarkJS full proof with Groth16. * @param witness The parameters for creating the proof. @@ -18,21 +12,43 @@ export default class RLN extends ZkProtocol { * @param finalZkeyPath The ZKey file path. * @returns The full SnarkJS proof. */ - public static async genProof(witness: any, wasmFilePath: string, finalZkeyPath: string): Promise { - const { proof, publicSignalsArray } = await groth16.fullProve(witness, wasmFilePath, finalZkeyPath, null) + /* istanbul ignore next */ + public static async genProof(witness: any, wasmFilePath: string, finalZkeyPath: string): Promise { + const { proof, publicSignals } = await groth16.fullProve(witness, wasmFilePath, finalZkeyPath, null) - if (publicSignalsArray.length !== RLN.PUBLIC_SIGNALS_COUNT) throw new Error("Error while generating proof") - - const publicSignals: RLNPublicSignals = { - yShare: publicSignalsArray[0], - merkleRoot: publicSignalsArray[1], - internalNullifier: publicSignalsArray[2], - signalHash: publicSignalsArray[3], - epoch: publicSignalsArray[4], - rlnIdentifier: publicSignalsArray[5] + return { + proof, + publicSignals: { + yShare: publicSignals[0], + merkleRoot: publicSignals[1], + internalNullifier: publicSignals[2], + signalHash: publicSignals[3], + epoch: publicSignals[4], + rlnIdentifier: publicSignals[5] + } } + } - return { proof, publicSignals } + /** + * Verifies a zero-knowledge SnarkJS proof. + * @param verificationKey The zero-knowledge verification key. + * @param fullProof The SnarkJS full proof. + * @returns True if the proof is valid, false otherwise. + */ + /* istanbul ignore next */ + public static verifyProof(verificationKey: string, { proof, publicSignals }: RLNFullProof): Promise { + return groth16.verify( + verificationKey, + [ + publicSignals.yShare, + publicSignals.merkleRoot, + publicSignals.internalNullifier, + publicSignals.signalHash, + publicSignals.epoch, + publicSignals.rlnIdentifier + ], + proof + ) } /** diff --git a/packages/protocols/src/semaphore.ts b/packages/protocols/src/semaphore.ts index 5e9738d..7a09dba 100644 --- a/packages/protocols/src/semaphore.ts +++ b/packages/protocols/src/semaphore.ts @@ -1,16 +1,10 @@ import { MerkleProof } from "@zk-kit/incremental-merkle-tree" import { poseidon } from "circomlibjs" import { groth16 } from "snarkjs" -import { FullProof, StrBigInt, SemaphoreWitness, SemaphorePublicSignals } from "./types" +import { SemaphoreFullProof, SemaphoreSolidityProof, SemaphoreWitness, StrBigInt } from "./types" import { genSignalHash } from "./utils" -import ZkProtocol from "./zk-protocol" - -export default class Semaphore extends ZkProtocol { - /** - * The number of public signals that should be returned by snarkjs when generating a proof. - */ - private static PUBLIC_SIGNALS_COUNT: number = 6 +export default class Semaphore { /** * Generates a SnarkJS full proof with Groth16. * @param witness The parameters for creating the proof. @@ -18,19 +12,39 @@ export default class Semaphore extends ZkProtocol { * @param finalZkeyPath The ZKey file path. * @returns The full SnarkJS proof. */ - public static async genProof(witness: any, wasmFilePath: string, finalZkeyPath: string): Promise { - const { proof, publicSignalsArray } = await groth16.fullProve(witness, wasmFilePath, finalZkeyPath, null) + /* istanbul ignore next */ + public static async genProof(witness: any, wasmFilePath: string, finalZkeyPath: string): Promise { + const { proof, publicSignals } = await groth16.fullProve(witness, wasmFilePath, finalZkeyPath, null) - if (publicSignalsArray.length !== Semaphore.PUBLIC_SIGNALS_COUNT) throw new Error("Error while generating proof") - - const publicSignals: SemaphorePublicSignals = { - merkleRoot: publicSignalsArray[0], - nullifierHash: publicSignalsArray[1], - signalHash: publicSignalsArray[2], - externalNullifier: publicSignalsArray[3] + return { + proof, + publicSignals: { + merkleRoot: publicSignals[0], + nullifierHash: publicSignals[1], + signalHash: publicSignals[2], + externalNullifier: publicSignals[3] + } } + } - return { proof, publicSignals } + /** + * Verifies a zero-knowledge SnarkJS proof. + * @param verificationKey The zero-knowledge verification key. + * @param fullProof The SnarkJS full proof. + * @returns True if the proof is valid, false otherwise. + */ + /* istanbul ignore next */ + public static verifyProof(verificationKey: string, { proof, publicSignals }: SemaphoreFullProof): Promise { + return groth16.verify( + verificationKey, + [ + publicSignals.merkleRoot, + publicSignals.nullifierHash, + publicSignals.signalHash, + publicSignals.externalNullifier + ], + proof + ) } /** @@ -70,4 +84,25 @@ export default class Semaphore extends ZkProtocol { public static genNullifierHash(externalNullifier: StrBigInt, identityNullifier: StrBigInt): bigint { return poseidon([BigInt(externalNullifier), BigInt(identityNullifier)]) } + + /** + * Converts a full proof in a proof compatible with the Verifier.sol method inputs. + * @param fullProof The proof generated with SnarkJS. + * @returns The Solidity compatible proof. + */ + /* istanbul ignore next */ + public static packToSolidityProof(fullProof: SemaphoreFullProof): SemaphoreSolidityProof { + const { proof } = fullProof + + return [ + proof.pi_a[0], + proof.pi_a[1], + proof.pi_b[0][1], + proof.pi_b[0][0], + proof.pi_b[1][1], + proof.pi_b[1][0], + proof.pi_c[0], + proof.pi_c[1] + ] + } } diff --git a/packages/protocols/src/types/index.ts b/packages/protocols/src/types/index.ts index df8d759..734111f 100644 --- a/packages/protocols/src/types/index.ts +++ b/packages/protocols/src/types/index.ts @@ -8,9 +8,14 @@ export type Proof = { curve: string } -export type FullProof = { +export type RLNFullProof = { proof: Proof - publicSignals: RLNPublicSignals | SemaphorePublicSignals + publicSignals: RLNPublicSignals +} + +export type SemaphoreFullProof = { + proof: Proof + publicSignals: SemaphorePublicSignals } export type RLNPublicSignals = { @@ -29,7 +34,16 @@ export type SemaphorePublicSignals = { externalNullifier: StrBigInt } -export type SolidityProof = StrBigInt[] +export type SemaphoreSolidityProof = [ + StrBigInt, + StrBigInt, + StrBigInt, + StrBigInt, + StrBigInt, + StrBigInt, + StrBigInt, + StrBigInt +] export type SemaphoreWitness = { identityNullifier: StrBigInt diff --git a/packages/protocols/src/zk-protocol.ts b/packages/protocols/src/zk-protocol.ts deleted file mode 100644 index 89db91b..0000000 --- a/packages/protocols/src/zk-protocol.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* istanbul ignore file */ -import { groth16 } from "snarkjs" -import { FullProof, SolidityProof, StrBigInt } from "./types" - -export default class ZkProtocol { - /** - * Verifies a zero-knowledge SnarkJS proof. - * @param verificationKey The zero-knowledge verification key. - * @param fullProof The SnarkJS full proof. - * @returns True if the proof is valid, false otherwise. - */ - public static verifyProof(verificationKey: string, fullProof: FullProof): Promise { - const { proof, publicSignals } = fullProof - - const publicSignalsArray: StrBigInt[] = Object.values(publicSignals) - - return groth16.verify(verificationKey, publicSignalsArray, proof) - } - - /** - * Converts a full proof in a proof compatible with the Verifier.sol method inputs. - * @param fullProof The proof generated with SnarkJS. - * @returns The Solidity compatible proof. - */ - public static packToSolidityProof(fullProof: FullProof): SolidityProof { - const { proof } = fullProof - - return [ - proof.pi_a[0], - proof.pi_a[1], - proof.pi_b[0][1], - proof.pi_b[0][0], - proof.pi_b[1][1], - proof.pi_b[1][0], - proof.pi_c[0], - proof.pi_c[1] - ] - } -} diff --git a/packages/protocols/tests/rln.test.ts b/packages/protocols/tests/rln.test.ts index 125412a..e96ec1d 100644 --- a/packages/protocols/tests/rln.test.ts +++ b/packages/protocols/tests/rln.test.ts @@ -58,6 +58,26 @@ describe("RLN", () => { expect(fun).toThrow("Can't generate a proof for a zero leaf") }) + it("Should retrieve user secret after spaming", () => { + const identity = new ZkIdentity() + const secretHash = identity.getSecretHash() + + const signal1 = "hey hey" + const signalHash1 = genSignalHash(signal1) + const signal2 = "hey hey again" + const signalHash2 = genSignalHash(signal2) + + const epoch = genExternalNullifier("test-epoch") + const rlnIdentifier = RLN.genIdentifier() + + const [y1] = RLN.calculateOutput(secretHash, BigInt(epoch), rlnIdentifier, signalHash1) + const [y2] = RLN.calculateOutput(secretHash, BigInt(epoch), rlnIdentifier, signalHash2) + + const retrievedSecret = RLN.retrieveSecret(signalHash1, signalHash2, y1, y2) + + expect(retrievedSecret).toEqual(secretHash) + }) + // eslint-disable-next-line jest/no-disabled-tests it.skip("Should generate rln proof and verify it", async () => { const identity = new ZkIdentity() @@ -96,27 +116,6 @@ describe("RLN", () => { const response = await RLN.verifyProof(vKey, { proof: fullProof.proof, publicSignals }) expect(response).toBe(true) - expect(fullProof.publicSignals).toEqual(publicSignals) }, 30000) - - it("Should retrieve user secret after spaming", () => { - const identity = new ZkIdentity() - const secretHash = identity.getSecretHash() - - const signal1 = "hey hey" - const signalHash1 = genSignalHash(signal1) - const signal2 = "hey hey again" - const signalHash2 = genSignalHash(signal2) - - const epoch = genExternalNullifier("test-epoch") - const rlnIdentifier = RLN.genIdentifier() - - const [y1] = RLN.calculateOutput(secretHash, BigInt(epoch), rlnIdentifier, signalHash1) - const [y2] = RLN.calculateOutput(secretHash, BigInt(epoch), rlnIdentifier, signalHash2) - - const retrievedSecret = RLN.retrieveSecret(signalHash1, signalHash2, y1, y2) - - expect(retrievedSecret).toEqual(secretHash) - }) }) }) diff --git a/packages/protocols/tests/semaphore.test.ts b/packages/protocols/tests/semaphore.test.ts index 9718254..2b9f93e 100644 --- a/packages/protocols/tests/semaphore.test.ts +++ b/packages/protocols/tests/semaphore.test.ts @@ -76,7 +76,6 @@ describe("Semaphore", () => { const response = await Semaphore.verifyProof(vKey, { proof: fullProof.proof, publicSignals }) expect(response).toBe(true) - expect(fullProof.publicSignals).toEqual(publicSignals) }, 30000) }) })