From 1aed70e9bc41512f6838b00c576399042c3160e9 Mon Sep 17 00:00:00 2001
From: ctrlc03 <93448202+ctrlc03@users.noreply.github.com>
Date: Tue, 28 Feb 2023 16:37:21 +0000
Subject: [PATCH] feat(verification): added actions helper to extract artifacts
 from a zKey

Added helper functions to extract the solidity verifier and the verification key from a zkey
---
 packages/actions/src/helpers/verification.ts  |  67 +++++++++++++++++-
 packages/actions/src/index.ts                 |   8 ++-
 .../test/data/circuit-small_00001.zkey        | Bin 0 -> 3730 bytes
 .../actions/test/unit/verification.test.ts    |  65 +++++++++++++++++
 4 files changed, 138 insertions(+), 2 deletions(-)
 create mode 100644 packages/actions/test/data/circuit-small_00001.zkey
 create mode 100644 packages/actions/test/unit/verification.test.ts

diff --git a/packages/actions/src/helpers/verification.ts b/packages/actions/src/helpers/verification.ts
index 625f2201..4cd199f4 100644
--- a/packages/actions/src/helpers/verification.ts
+++ b/packages/actions/src/helpers/verification.ts
@@ -1,6 +1,10 @@
 import { DocumentData, Firestore } from "firebase/firestore"
-import { FirebaseDocumentInfo } from "../types"
+import { zKey } from "snarkjs"
+import fs from "fs"
+import path from "path"
+import { cwd } from "process"
 import { getCurrentContributorContribution } from "./database"
+import { FirebaseDocumentInfo } from "../types"
 
 /**
  * Return an array of true of false based on contribution verification result per each circuit.
@@ -121,3 +125,64 @@ export const getValidContributionAttestation = async (
 
     return attestation
 }
+
+/**
+ * Helper method to extract the Solidity verifier
+ * from a final zKey file and save it to a local file.
+ * @param solidityVersion <string> The solidity version to include in the verifier pragma definition.
+ * @param finalZkeyPath <string> The path to the zKey file.
+ * @param verifierLocalPath <string> The path to the local file where the verifier will be saved.
+ */
+export const exportVerifierContract = async (
+    solidityVersion: string,
+    finalZkeyPath: string,
+    verifierLocalPath: string
+) => {
+    // Extract verifier.
+
+    let verifierCode = await zKey.exportSolidityVerifier(
+        finalZkeyPath,
+        {
+            groth16: fs
+                .readFileSync(path.join(cwd(), "node_modules/snarkjs/templates/verifier_groth16.sol.ejs"))
+                .toString()
+        },
+        console
+    )
+
+    // Update solidity version.
+    verifierCode = verifierCode.replace(
+        /pragma solidity \^\d+\.\d+\.\d+/,
+        `pragma solidity ^${solidityVersion || "0.8.0"}`
+    )
+
+    fs.writeFileSync(verifierLocalPath, verifierCode)
+}
+
+/**
+ * Helpers method to extract the vKey from a final zKey file
+ * @param finalZkeyPath <string> The path to the zKey file.
+ * @param vKeyLocalPath <string> The path to the local file where the vKey will be saved.
+ */
+export const exportVkey = async (finalZkeyPath: string, vKeyLocalPath: string) => {
+    const verificationKeyJSONData = await zKey.exportVerificationKey(finalZkeyPath)
+    fs.writeFileSync(vKeyLocalPath, JSON.stringify(verificationKeyJSONData))
+}
+
+/**
+ * Helper method to extract the Solidity verifier and the Verification key
+ * from a final zKey file and save them to local files.
+ * @param solidityVersion <string> The solidity version to include in the verifier pragma definition.
+ * @param finalZkeyPath <string> The path to the zKey file.
+ * @param verifierLocalPath <string> The path to the local file where the verifier will be saved.
+ * @param vKeyLocalPath <string> The path to the local file where the vKey will be saved.
+ */
+export const exportVerifierAndVKey = async (
+    solidityVersion: string,
+    finalZkeyPath: string,
+    verifierLocalPath: string,
+    vKeyLocalPath: string
+) => {
+    await exportVerifierContract(solidityVersion, finalZkeyPath, verifierLocalPath)
+    await exportVkey(finalZkeyPath, vKeyLocalPath)
+}
diff --git a/packages/actions/src/index.ts b/packages/actions/src/index.ts
index c3e2c9ef..59f9ffa1 100644
--- a/packages/actions/src/index.ts
+++ b/packages/actions/src/index.ts
@@ -43,7 +43,13 @@ export {
     getContributionsCollectionPath,
     getTimeoutsCollectionPath
 } from "./helpers/database"
-export { getContributorContributionsVerificationResults, getValidContributionAttestation } from "./helpers/verification"
+export {
+    getContributorContributionsVerificationResults,
+    getValidContributionAttestation,
+    exportVerifierAndVKey,
+    exportVerifierContract,
+    exportVkey
+} from "./helpers/verification"
 export { initializeFirebaseCoreServices } from "./helpers/services"
 export { signInToFirebaseWithCredentials, getCurrentFirebaseAuthUser, isCoordinator } from "./helpers/authentication"
 export {
diff --git a/packages/actions/test/data/circuit-small_00001.zkey b/packages/actions/test/data/circuit-small_00001.zkey
new file mode 100644
index 0000000000000000000000000000000000000000..7e2afc6cd05630b2eb425df63b47100b866dccde
GIT binary patch
literal 3730
zcmeHKdo+}JAD>|w8H^c&h^$+~xZlaGL9P?IgvKS2!pzXjghAsnge{~|QnOeUNx8O&
zl1oB{p)@5)lA<v!3N1BecV<uh^Zv7Y&i=9QIq&;9=leX*dCuqez5TwQ^Lw5Y{E=iJ
z004jpOn3(gObFq#h``PZF9N6v%ypCcTs~jbwtz{BrsHTOp$BNRR%h=vRE6;j*(=gW
z$PuhtKDRdGL?^Z<yS+En{!b2Az!AC>#@Pmi>$F$0fW7#eu4n_^C}$C~eFpx@cJyF~
z^hFUaSS1hoY+4UvY|)4c+UEGyHc2FH=!SVZyy%FH*9#vP@I~~7^COT-8Lq;*gt2e<
zYIaiJh3VT20}FQOXx2wP1)VEF)%Le}te;!tz%IB?`$-SMJB_LMne5)+oW({2=(Sc9
zz~gE(ThbfUe5nVO98HO;=F>oHX)4FTL5V9m2kegyMk<e)D*eob#-IQXyKvhk<}}gd
zu52lw;aMiif-cLg&WbB!!46q2<?_gyU(D7AWcn*#%=lfdIh`P9=C)pwBBiNtJ>kbE
zJalXO1~XVAAB)vr?H|4=I)7gWsoEj(xFzch8F<3oT#@^7dONMe71{RH#`{8pgH@m+
zI(+Z-)Z@(!51xx02%2&_lw(-o%k$=l<4-JDXD#z(KXdRumVGG;Px`5Rtz;~n&rem-
zh}d5KJJ_+jo}0F*y%?Y{5D+XLdDk}8M13RhWg^Vnw9|aUKQnUk++0Pa6mI<rh@uqu
zdnPtDby-#B+_IF9OW4!F#rHOF{@DR<r6VV6jWksIC&dCwQNe@Gr&U0orWHyN4@(=I
zXq234I0Ap(D-%*Q6M{LN@mcb7wQX;R%8>;{-p(nie2$nE^z?0Afd6t)mM2-`1>)8f
zr*iUDcI@Z5SdAb>v75#g@+@F%B|03eR^j-GrMs~ydsE_QUd?Q$x2d}QWzL0iSO9;g
z<Rf~Qn^8OwcX9olGT&y&4I^PdbM>jq8-%`-6cx-+x4?vuV6hrSVrVOutWRO>!VX`~
z9=y~|L6CmbjWHa#X6Yl(@U*5V&sB`%M1YPH+YA!ynxjq^FynXwyn-iN3|1jSsb9_P
zbBEfjch{chG4$7W?PcyTUYL0}XT*b{Xtw(CKsCJMv}CdYww$qJYiEKV(w|f*RRMN7
zv41dRsX)}ma5(YF3NNz&U>yJJuvYB_62BETr4Z;m=6dWg28(bzsh+&M*=onD1-}QM
z5>!pzxWb_Dq4R;ely5Pu6#C&pJxB^gCxB2R-ywVzRP@ThOB$tJFCTj0LY+B#e7sLT
z`94_hWbkZwPpYTEckY(I!TlEEPyJQLs?|HswJFVFLuQ+-`F`0Mcm3?L-1DpUc%?Tz
zlK)fr-}!%sD_kJt{~fMy?}Yrn;tFfZ_?E<SRoq=Strs>K{wDSBfC1L2t8~`-W|Bx%
zKEv|mCKSQO*&tH;nIIbvsJ-m^q~QX~;<g&4YQF(S3;s^0TLdph=lIjk?7ViwH_iwC
zS)<<qIZY?p7B3|dkKUn;AV`gnl^BTLVZs?+XnxY_ZpH$6^&0vBE81jb*Hj#4$1bQM
zPISdNqPAN$tGHgLH7DQ4DMxm+7kbP^nl<_xC<2}DOUyN%?0xlXX>rPCDLq*K>z;?N
zw%T1apa0x^NK;VCzltV9xnEb^_eOX_AZNVk+Wg38QEJHji|f1=jA|?_bX#uOND4lN
zS=mSZ6zM_`Z+?CBOK~PNyZz+7p1J12QWfD`e&36KwUGaU;D6Q2?y&mMo9Y221D@%k
z7(Qi|`Z0%VYB|KOd~@u}z0LvnKP^E_=s+l<|KoqY1IvnK?<<Wj4zG6Jy0s&l)ljP}
zc_NSbK+36VSjR&ftVxWvbHWqdP*2A;Fxaza)tlbRxOn$ceYI19<@zHTKo}PVYQ%|?
z3cf~iIx_Cp0U}4@%%`s!UgIiCmMrQ6yvD4|cd?!BZJxBZkC)|2uaQ(*)zXLNsl^`C
zTAdT9e|rPA3KHo4mV_vek*9%9xX<HS6pi%Lh8*k+rRW5XuVjzMO=m!*GK5T-P`Nca
zw{Uqz1_ZD*CTYy;21Uvv@99q!niLZy&gIY_!*HIqsL24{%eLOWLh2;l9ACSec+y<i
zPYwwPA2*BIUi0GU3)NFg<E||4CLWHR_|9GKjYh4<w0VO@JMmda;m6~y2&0eB5U$}y
zBNHPNvxZ8SACPlDF<Q06$l9qLK!WG}EgbCi>S2?IR%`DOFloI*^LOuom4Jhy;=($<
zop<N!UUo0`C|+)P+Xi8u-nJ!1+p?!$ia^$VlglAouM?A0$VE0f#UPWiq3${#V#&KG
z71f+KT;wl4-HIs#eSoZ_@dXF9q1uC6^d%-OE_NM$aOXLpBdA2LM)X1OSc4MEw0_T{
zjE05$rfXsQveNRqB8=5Ko+iV1hc8z1A5ZNFJ1Rpb{NfS%n!CE_x*3FtAq7;zTQ_#*
zVb;^RVF}VY7N*811=A+mLD3(km}L{$;CgH6;oJKa+q%qP6$A3=LpPezYO8*bR<8K!
zQfxYNOLHc^ZfPdyxTlxP71|cvBeZxK7xsy#@oTnr9n0L{q>m(#pZwiBvphx#k8+rB
z;Xld3w_eCNc{Ml`NT}*b@zUadKvErb4U?nfI3!X^RsWr?+jwRaGH+l{MB(e=`&H;W
z3biNtNXpi0v@3C1D(Bh3=QM?G{^|Gb2eFzF2l6?yvi(j;5S8nZI@T)2Q$`!P+U+Tb
zw_AIa=!`Mche4#Mx*8LmFxQb@$6TrQS%B04Gs`7Z83bTe>^k?usj#(E*omO268Z5a
z8bR4()X|66=P~kpve3{vqnF)SA5m~a$*7k5r2ScO%b>2VzM@p#lW{j<df-+l0jHL(
zsMNUkv2FbmNV`mY1AAUNZu|~qGMLBUlq&vaKkrve!SZ|j#78T#%i+1FrU>Y&8O!qY
zyH!n`U$jg%?J~mSQ~cpJY0?LuJ&l4<GqL24ONYV&L&z0n<_fP@C--`Cjbtr5{mby;
z%7i>J=rFY;I{Ff|;lQ|r!q#<i0sHN#t06Ikodx<-N}+y>wR4MEO++9yv1ry2P5r%3
zUA$=b<9(v8^OK-NLlgGih?d`^Z$O~il{~BRUpop%TSZ_%WtT`wjEV0)E3A(vHrfR1
R?n`tG_j5XobFkWn`2$Yrc;Wy6

literal 0
HcmV?d00001

diff --git a/packages/actions/test/unit/verification.test.ts b/packages/actions/test/unit/verification.test.ts
new file mode 100644
index 00000000..1d1b4285
--- /dev/null
+++ b/packages/actions/test/unit/verification.test.ts
@@ -0,0 +1,65 @@
+import chai, { expect } from "chai"
+import chaiAsPromised from "chai-as-promised"
+import dotenv from "dotenv"
+import { cwd } from "process"
+import fs from "fs"
+import { exportVerifierAndVKey, exportVerifierContract, exportVkey } from "../../src"
+import { envType } from "../utils"
+import { TestingEnvironment } from "../../src/types/enums"
+
+chai.use(chaiAsPromised)
+dotenv.config()
+
+/**
+ * Unit test for Verification utilities.
+ */
+
+describe("Verification utilities", () => {
+    const finalZkeyPath = `${cwd()}/packages/actions/test/data/circuit-small_00001.zkey`
+    const verifierExportPath = `${cwd()}/packages/actions/test/data/verifier.sol`
+    const vKeyExportPath = `${cwd()}/packages/actions/test/data/vkey.json`
+    const solidityVersion = "0.8.10"
+    describe("exportVerifierContract", () => {
+        if (envType === TestingEnvironment.PRODUCTION) {
+            it("should export the verifier contract", async () => {
+                await exportVerifierContract(solidityVersion, finalZkeyPath, verifierExportPath)
+                expect(fs.existsSync(verifierExportPath)).to.be.true
+            })
+        }
+        it("should fail when the zkey is not found", async () => {
+            await expect(exportVerifierContract("0.8.0", "invalid-path", verifierExportPath)).to.be.rejected
+        })
+    })
+    describe("exportVkey", () => {
+        if (envType === TestingEnvironment.PRODUCTION) {
+            it("should export the vkey", async () => {
+                await exportVkey(finalZkeyPath, vKeyExportPath)
+                expect(fs.existsSync(vKeyExportPath)).to.be.true
+            })
+        }
+        it("should fail when the zkey is not found", async () => {
+            await expect(exportVkey("invalid-path", vKeyExportPath)).to.be.rejected
+        })
+    })
+    describe("exportVerifierAndVKey", () => {
+        if (envType === TestingEnvironment.PRODUCTION) {
+            it("should export the verifier contract and the vkey", async () => {
+                await exportVerifierAndVKey("0.8.0", finalZkeyPath, verifierExportPath, vKeyExportPath)
+                expect(fs.existsSync(verifierExportPath)).to.be.true
+                expect(fs.existsSync(vKeyExportPath)).to.be.true
+            })
+        }
+        it("should fail when the zkey is not found", async () => {
+            await expect(exportVerifierAndVKey("0.8.0", "invalid-path", verifierExportPath, vKeyExportPath)).to.be
+                .rejected
+        })
+    })
+    afterAll(() => {
+        if (fs.existsSync(verifierExportPath)) {
+            fs.unlinkSync(verifierExportPath)
+        }
+        if (fs.existsSync(vKeyExportPath)) {
+            fs.unlinkSync(vKeyExportPath)
+        }
+    })
+})