@devtion/actions 0.0.0-7e983e3

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.
Files changed (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +83 -0
  3. package/dist/index.mjs +2608 -0
  4. package/dist/index.node.js +2714 -0
  5. package/dist/types/hardhat.config.d.ts +6 -0
  6. package/dist/types/hardhat.config.d.ts.map +1 -0
  7. package/dist/types/src/helpers/authentication.d.ts +21 -0
  8. package/dist/types/src/helpers/authentication.d.ts.map +1 -0
  9. package/dist/types/src/helpers/constants.d.ts +194 -0
  10. package/dist/types/src/helpers/constants.d.ts.map +1 -0
  11. package/dist/types/src/helpers/contracts.d.ts +57 -0
  12. package/dist/types/src/helpers/contracts.d.ts.map +1 -0
  13. package/dist/types/src/helpers/crypto.d.ts +27 -0
  14. package/dist/types/src/helpers/crypto.d.ts.map +1 -0
  15. package/dist/types/src/helpers/database.d.ts +105 -0
  16. package/dist/types/src/helpers/database.d.ts.map +1 -0
  17. package/dist/types/src/helpers/functions.d.ts +145 -0
  18. package/dist/types/src/helpers/functions.d.ts.map +1 -0
  19. package/dist/types/src/helpers/security.d.ts +10 -0
  20. package/dist/types/src/helpers/security.d.ts.map +1 -0
  21. package/dist/types/src/helpers/services.d.ts +38 -0
  22. package/dist/types/src/helpers/services.d.ts.map +1 -0
  23. package/dist/types/src/helpers/storage.d.ts +121 -0
  24. package/dist/types/src/helpers/storage.d.ts.map +1 -0
  25. package/dist/types/src/helpers/tasks.d.ts +2 -0
  26. package/dist/types/src/helpers/tasks.d.ts.map +1 -0
  27. package/dist/types/src/helpers/utils.d.ts +139 -0
  28. package/dist/types/src/helpers/utils.d.ts.map +1 -0
  29. package/dist/types/src/helpers/verification.d.ts +95 -0
  30. package/dist/types/src/helpers/verification.d.ts.map +1 -0
  31. package/dist/types/src/helpers/vm.d.ts +112 -0
  32. package/dist/types/src/helpers/vm.d.ts.map +1 -0
  33. package/dist/types/src/index.d.ts +15 -0
  34. package/dist/types/src/index.d.ts.map +1 -0
  35. package/dist/types/src/types/enums.d.ts +133 -0
  36. package/dist/types/src/types/enums.d.ts.map +1 -0
  37. package/dist/types/src/types/index.d.ts +603 -0
  38. package/dist/types/src/types/index.d.ts.map +1 -0
  39. package/package.json +87 -0
  40. package/src/helpers/authentication.ts +37 -0
  41. package/src/helpers/constants.ts +312 -0
  42. package/src/helpers/contracts.ts +268 -0
  43. package/src/helpers/crypto.ts +55 -0
  44. package/src/helpers/database.ts +221 -0
  45. package/src/helpers/functions.ts +438 -0
  46. package/src/helpers/security.ts +86 -0
  47. package/src/helpers/services.ts +83 -0
  48. package/src/helpers/storage.ts +329 -0
  49. package/src/helpers/tasks.ts +56 -0
  50. package/src/helpers/utils.ts +743 -0
  51. package/src/helpers/verification.ts +354 -0
  52. package/src/helpers/vm.ts +392 -0
  53. package/src/index.ts +162 -0
  54. package/src/types/enums.ts +141 -0
  55. package/src/types/index.ts +650 -0
@@ -0,0 +1,268 @@
1
+ import { Contract, ContractFactory, Signer } from "ethers"
2
+ import { utils as ffUtils } from "ffjavascript"
3
+ import { Firestore, where } from "firebase/firestore"
4
+ import { Functions } from "firebase/functions"
5
+ import fs from "fs"
6
+ import solc from "solc"
7
+ import {
8
+ downloadAllCeremonyArtifacts,
9
+ exportVerifierAndVKey,
10
+ generateGROTH16Proof,
11
+ generateZkeyFromScratch,
12
+ getFinalContributionBeacon,
13
+ verifyGROTH16Proof,
14
+ verifyZKey
15
+ } from "./verification"
16
+ import { compareHashes } from "./crypto"
17
+ import { commonTerms, finalContributionIndex, verificationKeyAcronym, verifierSmartContractAcronym } from "./constants"
18
+ import { fromQueryToFirebaseDocumentInfo, queryCollection } from "./database"
19
+
20
+ /**
21
+ * Formats part of a GROTH16 SNARK proof
22
+ * @link adapted from SNARKJS p256 function
23
+ * @param proofPart <any> a part of a proof to be formatted
24
+ * @returns <string> the formatted proof part
25
+ */
26
+ export const p256 = (proofPart: any) => {
27
+ let nProofPart = proofPart.toString(16)
28
+ while (nProofPart.length < 64) nProofPart = `0${nProofPart}`
29
+ nProofPart = `0x${nProofPart}`
30
+ return nProofPart
31
+ }
32
+
33
+ /**
34
+ * This function formats the calldata for Solidity
35
+ * @link adapted from SNARKJS formatSolidityCalldata function
36
+ * @dev this function is supposed to be called with
37
+ * @dev the output of generateGROTH16Proof
38
+ * @param circuitInput <string[]> Inputs to the circuit
39
+ * @param _proof <object> Proof
40
+ * @returns <SolidityCalldata> The calldata formatted for Solidity
41
+ */
42
+ export const formatSolidityCalldata = (circuitInput: string[], _proof: any): any => {
43
+ try {
44
+ const proof = ffUtils.unstringifyBigInts(_proof)
45
+ // format the public inputs to the circuit
46
+ const formattedCircuitInput = []
47
+ for (const cInput of circuitInput) {
48
+ formattedCircuitInput.push(p256(ffUtils.unstringifyBigInts(cInput)))
49
+ }
50
+ // construct calldata
51
+ const calldata = {
52
+ arg1: [p256(proof.pi_a[0]), p256(proof.pi_a[1])],
53
+ arg2: [
54
+ [p256(proof.pi_b[0][1]), p256(proof.pi_b[0][0])],
55
+ [p256(proof.pi_b[1][1]), p256(proof.pi_b[1][0])]
56
+ ],
57
+ arg3: [p256(proof.pi_c[0]), p256(proof.pi_c[1])],
58
+ arg4: formattedCircuitInput
59
+ }
60
+ return calldata
61
+ } catch (error: any) {
62
+ throw new Error(
63
+ "There was an error while formatting the calldata. Please make sure that you are calling this function with the output of the generateGROTH16Proof function, and then please try again."
64
+ )
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Verify a GROTH16 SNARK proof on chain
70
+ * @param contract <Contract> The contract instance
71
+ * @param proof <SolidityCalldata> The calldata formatted for Solidity
72
+ * @returns <Promise<boolean>> Whether the proof is valid or not
73
+ */
74
+ export const verifyGROTH16ProofOnChain = async (contract: any, proof: any): Promise<boolean> => {
75
+ const res = await contract.verifyProof(proof.arg1, proof.arg2, proof.arg3, proof.arg4)
76
+ return res
77
+ }
78
+
79
+ /**
80
+ * Compiles a contract given a path
81
+ * @param contractPath <string> path to the verifier contract
82
+ * @returns <Promise<any>> the compiled contract
83
+ */
84
+ export const compileContract = async (contractPath: string): Promise<any> => {
85
+ if (!fs.existsSync(contractPath))
86
+ throw new Error(
87
+ "The contract path does not exist. Please make sure that you are passing a valid path to the contract and try again."
88
+ )
89
+
90
+ const data = fs.readFileSync(contractPath).toString()
91
+ const input = {
92
+ language: "Solidity",
93
+ sources: {
94
+ Verifier: { content: data }
95
+ },
96
+ settings: {
97
+ outputSelection: {
98
+ "*": {
99
+ "*": ["*"]
100
+ }
101
+ }
102
+ }
103
+ }
104
+
105
+ try {
106
+ const compiled = JSON.parse(solc.compile(JSON.stringify(input), { import: { contents: "" } }))
107
+ return compiled.contracts.Verifier.Verifier
108
+ } catch (error: any) {
109
+ throw new Error(
110
+ "There was an error while compiling the smart contract. Please check that the file is not corrupted and try again."
111
+ )
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Deploy the verifier contract
117
+ * @param contractFactory <ContractFactory> The contract factory
118
+ * @returns <Promise<Contract>> The contract instance
119
+ */
120
+ export const deployVerifierContract = async (contractPath: string, signer: Signer): Promise<Contract> => {
121
+ const compiledContract = await compileContract(contractPath)
122
+ // connect to hardhat node running locally
123
+ const contractFactory = new ContractFactory(compiledContract.abi, compiledContract.evm.bytecode.object, signer)
124
+ const contract = await contractFactory.deploy()
125
+ await contract.deployed()
126
+ return contract
127
+ }
128
+
129
+ /**
130
+ * Verify a ceremony validity
131
+ * 1. Download all artifacts
132
+ * 2. Verify that the zkeys are valid
133
+ * 3. Extract the verifier and the vKey
134
+ * 4. Generate a proof and verify it locally
135
+ * 5. Deploy Verifier contract and verify the proof on-chain
136
+ * @param functions <Functions> firebase functions instance
137
+ * @param firestore <Firestore> firebase firestore instance
138
+ * @param ceremonyPrefix <string> ceremony prefix
139
+ * @param outputDirectory <string> output directory where to store the ceremony artifacts
140
+ * @param circuitInputsPath <string> path to the circuit inputs file
141
+ * @param verifierTemplatePath <string> path to the verifier template file
142
+ * @param signer <Signer> signer for contract interaction
143
+ * @param logger <any> logger for printing snarkjs output
144
+ */
145
+ export const verifyCeremony = async (
146
+ functions: Functions,
147
+ firestore: Firestore,
148
+ ceremonyPrefix: string,
149
+ outputDirectory: string,
150
+ circuitInputsPath: string,
151
+ verifierTemplatePath: string,
152
+ signer: Signer,
153
+ logger?: any
154
+ ): Promise<void> => {
155
+ // 1. download all ceremony artifacts
156
+ const ceremonyArtifacts = await downloadAllCeremonyArtifacts(functions, firestore, ceremonyPrefix, outputDirectory)
157
+ // if there are no ceremony artifacts, we throw an error
158
+ if (ceremonyArtifacts.length === 0)
159
+ throw new Error(
160
+ "There was an error while downloading all ceremony artifacts. Please review your ceremony prefix and try again."
161
+ )
162
+
163
+ // extract the circuit inputs
164
+ if (!fs.existsSync(circuitInputsPath))
165
+ throw new Error("The circuit inputs file does not exist. Please check the path and try again.")
166
+ const circuitsInputs = JSON.parse(fs.readFileSync(circuitInputsPath).toString())
167
+
168
+ // find the ceremony given the prefix
169
+ const ceremonyQuery = await queryCollection(firestore, commonTerms.collections.ceremonies.name, [
170
+ where(commonTerms.collections.ceremonies.fields.prefix, "==", ceremonyPrefix)
171
+ ])
172
+
173
+ // get the ceremony data - no need to do an existence check as
174
+ // we already checked that the ceremony exists in downloafAllCeremonyArtifacts
175
+ const ceremonyData = fromQueryToFirebaseDocumentInfo(ceremonyQuery.docs)
176
+ const ceremony = ceremonyData.at(0)
177
+ // this is required to re-generate the final zKey
178
+ const { coordinatorId } = ceremony!.data
179
+ const ceremonyId = ceremony!.id
180
+
181
+ // we verify each circuit separately
182
+ for (const ceremonyArtifact of ceremonyArtifacts) {
183
+ // get the index of the circuit in the list of circuits
184
+ const inputIndex = ceremonyArtifacts.indexOf(ceremonyArtifact)
185
+
186
+ // 2. verify the final zKey
187
+ const isValid = await verifyZKey(
188
+ ceremonyArtifact.r1csLocalFilePath,
189
+ ceremonyArtifact.finalZkeyLocalFilePath,
190
+ ceremonyArtifact.potLocalFilePath,
191
+ logger
192
+ )
193
+
194
+ if (!isValid)
195
+ throw new Error(
196
+ `The zkey for Circuit ${ceremonyArtifact.circuitPrefix} is not valid. Please check that the artifact is correct. If not, you might have to re run the final contribution to compute a valid final zKey.`
197
+ )
198
+
199
+ // 3. get the final contribution beacon
200
+ const contributionBeacon = await getFinalContributionBeacon(
201
+ firestore,
202
+ ceremonyId,
203
+ ceremonyArtifact.circuitId,
204
+ coordinatorId
205
+ )
206
+ const generatedFinalZkeyPath = `${ceremonyArtifact.directoryRoot}/${ceremonyArtifact.circuitPrefix}_${finalContributionIndex}_verification.zkey`
207
+ // 4. re generate the zkey using the beacon and check hashes
208
+ await generateZkeyFromScratch(
209
+ true,
210
+ ceremonyArtifact.r1csLocalFilePath,
211
+ ceremonyArtifact.potLocalFilePath,
212
+ generatedFinalZkeyPath,
213
+ logger,
214
+ ceremonyArtifact.lastZkeyLocalFilePath,
215
+ coordinatorId,
216
+ contributionBeacon
217
+ )
218
+ const zKeysMatching = await compareHashes(generatedFinalZkeyPath, ceremonyArtifact.finalZkeyLocalFilePath)
219
+ if (!zKeysMatching)
220
+ throw new Error(
221
+ `The final zkey for the Circuit ${ceremonyArtifact.circuitPrefix} does not match the one generated from the beacon. Please confirm manually by downloading from the S3 bucket.`
222
+ )
223
+
224
+ // 5. extract the verifier and the vKey
225
+ const verifierLocalPath = `${ceremonyArtifact.directoryRoot}/${ceremonyArtifact.circuitPrefix}_${verifierSmartContractAcronym}_verification.sol`
226
+ const vKeyLocalPath = `${ceremonyArtifact.directoryRoot}/${ceremonyArtifact.circuitPrefix}_${verificationKeyAcronym}_verification.json`
227
+ await exportVerifierAndVKey(
228
+ ceremonyArtifact.finalZkeyLocalFilePath,
229
+ verifierLocalPath,
230
+ vKeyLocalPath,
231
+ verifierTemplatePath
232
+ )
233
+
234
+ // 6. verify that the generated verifier and vkey match the ones downloaded from S3
235
+ const verifierMatching = await compareHashes(verifierLocalPath, ceremonyArtifact.verifierLocalFilePath)
236
+ if (!verifierMatching)
237
+ throw new Error(
238
+ `The verifier contract for the Contract ${ceremonyArtifact.circuitPrefix} does not match the one downloaded from S3. Please confirm manually by downloading from the S3 bucket.`
239
+ )
240
+ const vKeyMatching = await compareHashes(vKeyLocalPath, ceremonyArtifact.verificationKeyLocalFilePath)
241
+ if (!vKeyMatching)
242
+ throw new Error(
243
+ `The verification key for the Contract ${ceremonyArtifact.circuitPrefix} does not match the one downloaded from S3. Please confirm manually by downloading from the S3 bucket.`
244
+ )
245
+
246
+ // 7. generate a proof and verify it locally (use either of the downloaded or generated as the hashes will have matched at this point)
247
+ const { proof, publicSignals } = await generateGROTH16Proof(
248
+ circuitsInputs[inputIndex],
249
+ ceremonyArtifact.finalZkeyLocalFilePath,
250
+ ceremonyArtifact.wasmLocalFilePath,
251
+ logger
252
+ )
253
+ const isProofValid = await verifyGROTH16Proof(vKeyLocalPath, publicSignals, proof)
254
+ if (!isProofValid)
255
+ throw new Error(
256
+ `Could not verify the proof for Circuit ${ceremonyArtifact.circuitPrefix}. Please check that the artifacts are correct as well as the inputs to the circuit, and try again.`
257
+ )
258
+
259
+ // 8. deploy Verifier contract and verify the proof on-chain
260
+ const verifierContract = await deployVerifierContract(verifierLocalPath, signer)
261
+ const formattedProof = await formatSolidityCalldata(publicSignals, proof)
262
+ const isProofValidOnChain = await verifyGROTH16ProofOnChain(verifierContract, formattedProof)
263
+ if (!isProofValidOnChain)
264
+ throw new Error(
265
+ `Could not verify the proof on-chain for Circuit ${ceremonyArtifact.circuitPrefix}. Please check that the artifacts are correct as well as the inputs to the circuit, and try again.`
266
+ )
267
+ }
268
+ }
@@ -0,0 +1,55 @@
1
+ import fs from "fs"
2
+ import crypto from "crypto"
3
+ import blake from "blakejs"
4
+
5
+ /**
6
+ * @hidden
7
+ */
8
+ const toHexByte = (byte: number) => (byte < 0x10 ? `0${byte.toString(16)}` : byte.toString(16))
9
+
10
+ /**
11
+ * Converts Uint8Array to hexadecimal string.
12
+ * @param buffer arbritrary length of data
13
+ * @returns hexadecimal string
14
+ */
15
+ export const toHex = (buffer: Uint8Array): string => Array.from(buffer).map(toHexByte).join("")
16
+
17
+ /**
18
+ * Get 512 bit blake hash of the contents of given path.
19
+ * @param data buffer or hexadecimal string
20
+ * @returns 64 byte hexadecimal string
21
+ */
22
+ export const blake512FromPath = async (path: fs.PathLike): Promise<string> => {
23
+ const context = blake.blake2bInit(64, undefined)
24
+
25
+ const hash: string = await new Promise((resolve) => {
26
+ fs.createReadStream(path)
27
+ .on("data", (chunk: Buffer) => {
28
+ blake.blake2bUpdate(context, chunk)
29
+ })
30
+ .on("end", () => {
31
+ resolve(toHex(blake.blake2bFinal(context)))
32
+ })
33
+ })
34
+ return hash
35
+ }
36
+
37
+ /**
38
+ * Return the SHA256 hash (HEX format) of a given value
39
+ * @param value <string> - the value to be hashed.
40
+ * @returns <string> - the HEX format of the SHA256 hash of the given value
41
+ */
42
+ export const computeSHA256ToHex = (value: string): string => crypto.createHash("sha256").update(value).digest("hex")
43
+
44
+ /**
45
+ * Helper function that can be used to compare whether two files' hashes are equal or not.
46
+ * @param path1 <string> Path to the first file.
47
+ * @param path2 <string> Path to the second file.
48
+ * @returns <Promise<boolean>> Whether the files are equal or not.
49
+ */
50
+ export const compareHashes = async (path1: string, path2: string): Promise<boolean> => {
51
+ const hash1 = await blake512FromPath(path1)
52
+ const hash2 = await blake512FromPath(path2)
53
+
54
+ return hash1 === hash2
55
+ }
@@ -0,0 +1,221 @@
1
+ import {
2
+ collection as collectionRef,
3
+ doc,
4
+ DocumentData,
5
+ DocumentSnapshot,
6
+ Firestore,
7
+ getDoc,
8
+ getDocs,
9
+ query,
10
+ QueryConstraint,
11
+ QueryDocumentSnapshot,
12
+ QuerySnapshot,
13
+ Timestamp,
14
+ where
15
+ } from "firebase/firestore"
16
+ import { CeremonyState } from "../types/enums"
17
+ import { FirebaseDocumentInfo } from "../types/index"
18
+ import { commonTerms } from "./constants"
19
+
20
+ /**
21
+ * Get participants collection path for database reference.
22
+ * @notice all participants related documents are store under `ceremonies/<ceremonyId>/participants` collection path.
23
+ * nb. This is a rule that must be satisfied. This is NOT an optional convention.
24
+ * @param ceremonyId <string> - the unique identifier of the ceremony.
25
+ * @returns <string> - the participants collection path.
26
+ */
27
+ export const getParticipantsCollectionPath = (ceremonyId: string): string =>
28
+ `${commonTerms.collections.ceremonies.name}/${ceremonyId}/${commonTerms.collections.participants.name}`
29
+
30
+ /**
31
+ * Get circuits collection path for database reference.
32
+ * @notice all circuits related documents are store under `ceremonies/<ceremonyId>/circuits` collection path.
33
+ * nb. This is a rule that must be satisfied. This is NOT an optional convention.
34
+ * @param ceremonyId <string> - the unique identifier of the ceremony.
35
+ * @returns <string> - the participants collection path.
36
+ */
37
+ export const getCircuitsCollectionPath = (ceremonyId: string): string =>
38
+ `${commonTerms.collections.ceremonies.name}/${ceremonyId}/${commonTerms.collections.circuits.name}`
39
+
40
+ /**
41
+ * Get contributions collection path for database reference.
42
+ * @notice all contributions related documents are store under `ceremonies/<ceremonyId>/circuits/<circuitId>/contributions` collection path.
43
+ * nb. This is a rule that must be satisfied. This is NOT an optional convention.
44
+ * @param ceremonyId <string> - the unique identifier of the ceremony.
45
+ * @param circuitId <string> - the unique identifier of the circuit.
46
+ * @returns <string> - the contributions collection path.
47
+ */
48
+ export const getContributionsCollectionPath = (ceremonyId: string, circuitId: string): string =>
49
+ `${getCircuitsCollectionPath(ceremonyId)}/${circuitId}/${commonTerms.collections.contributions.name}`
50
+
51
+ /**
52
+ * Get timeouts collection path for database reference.
53
+ * @notice all timeouts related documents are store under `ceremonies/<ceremonyId>/participants/<participantId>/timeouts` collection path.
54
+ * nb. This is a rule that must be satisfied. This is NOT an optional convention.
55
+ * @param ceremonyId <string> - the unique identifier of the ceremony.
56
+ * @param participantId <string> - the unique identifier of the participant.
57
+ * @returns <string> - the timeouts collection path.
58
+ */
59
+ export const getTimeoutsCollectionPath = (ceremonyId: string, participantId: string): string =>
60
+ `${getParticipantsCollectionPath(ceremonyId)}/${participantId}/${commonTerms.collections.timeouts.name}`
61
+
62
+ /**
63
+ * Helper for query a collection based on certain constraints.
64
+ * @param firestoreDatabase <Firestore> - the Firestore service instance associated to the current Firebase application.
65
+ * @param collection <string> - the name of the collection.
66
+ * @param queryConstraints <Array<QueryConstraint>> - a sequence of where conditions.
67
+ * @returns <Promise<QuerySnapshot<DocumentData>>> - return the matching documents (if any).
68
+ */
69
+ export const queryCollection = async (
70
+ firestoreDatabase: Firestore,
71
+ collection: string,
72
+ queryConstraints: Array<QueryConstraint>
73
+ ): Promise<QuerySnapshot<DocumentData>> => {
74
+ // Make a query.
75
+ const q = query(collectionRef(firestoreDatabase, collection), ...queryConstraints)
76
+
77
+ // Get docs.
78
+ const snap = await getDocs(q)
79
+
80
+ return snap
81
+ }
82
+
83
+ /**
84
+ * Helper for obtaining uid and data for query document snapshots.
85
+ * @param queryDocSnap <Array<QueryDocumentSnapshot>> - the array of query document snapshot to be converted.
86
+ * @returns Array<FirebaseDocumentInfo>
87
+ */
88
+ export const fromQueryToFirebaseDocumentInfo = (
89
+ queryDocSnap: Array<QueryDocumentSnapshot>
90
+ ): Array<FirebaseDocumentInfo> =>
91
+ queryDocSnap.map((document: QueryDocumentSnapshot<DocumentData>) => ({
92
+ id: document.id,
93
+ ref: document.ref,
94
+ data: document.data()
95
+ }))
96
+
97
+ /**
98
+ * Fetch for all documents in a collection.
99
+ * @param firestoreDatabase <Firestore> - the Firestore service instance associated to the current Firebase application.
100
+ * @param collection <string> - the name of the collection.
101
+ * @returns <Promise<Array<QueryDocumentSnapshot<DocumentData>>>> - return all documents (if any).
102
+ */
103
+ export const getAllCollectionDocs = async (
104
+ firestoreDatabase: Firestore,
105
+ collection: string
106
+ ): Promise<Array<QueryDocumentSnapshot<DocumentData>>> =>
107
+ (await getDocs(collectionRef(firestoreDatabase, collection))).docs
108
+
109
+ /**
110
+ * Get a specific document from database.
111
+ * @param firestoreDatabase <Firestore> - the Firestore service instance associated to the current Firebase application.
112
+ * @param collection <string> - the name of the collection.
113
+ * @param documentId <string> - the unique identifier of the document in the collection.
114
+ * @returns <Promise<DocumentSnapshot<DocumentData>>> - return the document from Firestore.
115
+ */
116
+ export const getDocumentById = async (
117
+ firestoreDatabase: Firestore,
118
+ collection: string,
119
+ documentId: string
120
+ ): Promise<DocumentSnapshot<DocumentData>> => {
121
+ const docRef = doc(firestoreDatabase, collection, documentId)
122
+
123
+ return getDoc(docRef)
124
+ }
125
+
126
+ /**
127
+ * Query for opened ceremonies.
128
+ * @param firestoreDatabase <Firestore> - the Firestore service instance associated to the current Firebase application.
129
+ * @returns <Promise<Array<FirebaseDocumentInfo>>>
130
+ */
131
+ export const getOpenedCeremonies = async (firestoreDatabase: Firestore): Promise<Array<FirebaseDocumentInfo>> => {
132
+ const runningStateCeremoniesQuerySnap = await queryCollection(
133
+ firestoreDatabase,
134
+ commonTerms.collections.ceremonies.name,
135
+ [
136
+ where(commonTerms.collections.ceremonies.fields.state, "==", CeremonyState.OPENED),
137
+ where(commonTerms.collections.ceremonies.fields.endDate, ">=", Date.now())
138
+ ]
139
+ )
140
+
141
+ return fromQueryToFirebaseDocumentInfo(runningStateCeremoniesQuerySnap.docs)
142
+ }
143
+
144
+ /**
145
+ * Query for ceremony circuits.
146
+ * @notice the order by sequence position is fundamental to maintain parallelism among contributions for different circuits.
147
+ * @param firestoreDatabase <Firestore> - the Firestore service instance associated to the current Firebase application.
148
+ * @param ceremonyId <string> - the ceremony unique identifier.
149
+ * @returns Promise<Array<FirebaseDocumentInfo>> - the ceremony' circuits documents ordered by sequence position.
150
+ */
151
+ export const getCeremonyCircuits = async (
152
+ firestoreDatabase: Firestore,
153
+ ceremonyId: string
154
+ ): Promise<Array<FirebaseDocumentInfo>> =>
155
+ fromQueryToFirebaseDocumentInfo(
156
+ await getAllCollectionDocs(firestoreDatabase, getCircuitsCollectionPath(ceremonyId))
157
+ ).sort((a: FirebaseDocumentInfo, b: FirebaseDocumentInfo) => a.data.sequencePosition - b.data.sequencePosition)
158
+
159
+ /**
160
+ * Query for a specific ceremony' circuit contribution from a given contributor (if any).
161
+ * @notice if the caller is a coordinator, there could be more than one contribution (= the one from finalization applies to this criteria).
162
+ * @param firestoreDatabase <Firestore> - the Firestore service instance associated to the current Firebase application.
163
+ * @param ceremonyId <string> - the unique identifier of the ceremony.
164
+ * @param circuitId <string> - the unique identifier of the circuit.
165
+ * @param participantId <string> - the unique identifier of the participant.
166
+ * @returns <Promise<Array<FirebaseDocumentInfo>>> - the document info about the circuit contributions from contributor.
167
+ */
168
+ export const getCircuitContributionsFromContributor = async (
169
+ firestoreDatabase: Firestore,
170
+ ceremonyId: string,
171
+ circuitId: string,
172
+ participantId: string
173
+ ): Promise<Array<FirebaseDocumentInfo>> => {
174
+ const participantContributionsQuerySnap = await queryCollection(
175
+ firestoreDatabase,
176
+ getContributionsCollectionPath(ceremonyId, circuitId),
177
+ [where(commonTerms.collections.contributions.fields.participantId, "==", participantId)]
178
+ )
179
+
180
+ return fromQueryToFirebaseDocumentInfo(participantContributionsQuerySnap.docs)
181
+ }
182
+
183
+ /**
184
+ * Query for the active timeout from given participant for a given ceremony (if any).
185
+ * @param ceremonyId <string> - the identifier of the ceremony.
186
+ * @param participantId <string> - the identifier of the participant.
187
+ * @returns <Promise<Array<FirebaseDocumentInfo>>> - the document info about the current active participant timeout.
188
+ */
189
+ export const getCurrentActiveParticipantTimeout = async (
190
+ firestoreDatabase: Firestore,
191
+ ceremonyId: string,
192
+ participantId: string
193
+ ): Promise<Array<FirebaseDocumentInfo>> => {
194
+ const participantTimeoutQuerySnap = await queryCollection(
195
+ firestoreDatabase,
196
+ getTimeoutsCollectionPath(ceremonyId, participantId),
197
+ [where(commonTerms.collections.timeouts.fields.endDate, ">=", Timestamp.now().toMillis())]
198
+ )
199
+
200
+ return fromQueryToFirebaseDocumentInfo(participantTimeoutQuerySnap.docs)
201
+ }
202
+
203
+ /**
204
+ * Query for the closed ceremonies.
205
+ * @notice a ceremony is closed when the period for receiving new contributions has ended.
206
+ * @dev when the ceremony is closed it becomes ready for finalization.
207
+ * @param firestoreDatabase <Firestore> - the Firestore service instance associated to the current Firebase application.
208
+ * @returns <Promise<Array<FirebaseDocumentInfo>>> - the list of closed ceremonies.
209
+ */
210
+ export const getClosedCeremonies = async (firestoreDatabase: Firestore): Promise<Array<FirebaseDocumentInfo>> => {
211
+ const closedCeremoniesQuerySnap = await queryCollection(
212
+ firestoreDatabase,
213
+ commonTerms.collections.ceremonies.name,
214
+ [
215
+ where(commonTerms.collections.ceremonies.fields.state, "==", CeremonyState.CLOSED),
216
+ where(commonTerms.collections.ceremonies.fields.endDate, "<=", Date.now())
217
+ ]
218
+ )
219
+
220
+ return fromQueryToFirebaseDocumentInfo(closedCeremoniesQuerySnap.docs)
221
+ }