@devtion/devcli 0.0.0-57a8ab9 → 0.0.0-5fad82d

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.
@@ -41,7 +41,7 @@ import {
41
41
  } from "../lib/utils.js"
42
42
  import { COMMAND_ERRORS, showError } from "../lib/errors.js"
43
43
  import { authWithToken, bootstrapCommandExecutionAndServices, checkAuth } from "../lib/services.js"
44
- import { checkLocalBandadaIdentity, getAttestationLocalFilePath, localPaths } from "../lib/localConfigs.js"
44
+ import { getAttestationLocalFilePath, getLocalAuthMethod, localPaths } from "../lib/localConfigs.js"
45
45
  import theme from "../lib/theme.js"
46
46
  import { checkAndMakeNewDirectoryIfNonexistent, writeFile } from "../lib/files.js"
47
47
 
@@ -281,12 +281,12 @@ export const handleDiskSpaceRequirementForNextContribution = async (
281
281
  )} since is based on the aggregate free memory on your disks but some may not be detected!\n`
282
282
  )
283
283
 
284
- const { confirmation } = await askForConfirmation(
284
+ const { confirmationEnoughMemory } = await askForConfirmation(
285
285
  `Please, we kindly ask you to continue with the contribution if you have noticed the estimate is wrong and you have enough memory in your machine`,
286
286
  "Continue",
287
287
  "Exit"
288
288
  )
289
- wannaContributeOrHaveEnoughMemory = !!confirmation
289
+ wannaContributeOrHaveEnoughMemory = !!confirmationEnoughMemory
290
290
 
291
291
  if (circuitSequencePosition > 1) {
292
292
  console.log(
@@ -420,8 +420,8 @@ export const handlePublicAttestation = async (
420
420
  await sleep(1000) // workaround for file descriptor unexpected close.
421
421
 
422
422
  let gistUrl = ""
423
- const isBandada = checkLocalBandadaIdentity()
424
- if (!isBandada) {
423
+ const isGithub = getLocalAuthMethod() === "github"
424
+ if (isGithub) {
425
425
  gistUrl = await publishGist(participantAccessToken, publicAttestation, ceremonyName, ceremonyPrefix)
426
426
 
427
427
  console.log(
@@ -519,6 +519,8 @@ export const listenToCeremonyCircuitDocumentChanges = (
519
519
  })
520
520
  }
521
521
 
522
+ let contributionInProgress = false
523
+
522
524
  /**
523
525
  * Listen to current authenticated participant document changes.
524
526
  * @dev this is the core business logic related to the execution of the contribute command.
@@ -711,6 +713,12 @@ export const listenToParticipantDocumentChanges = async (
711
713
 
712
714
  // Scenario (3.B).
713
715
  if (isCurrentContributor && hasResumableStep && startingOrResumingContribution) {
716
+ if (contributionInProgress) {
717
+ console.warn(
718
+ `\n${theme.symbols.warning} Received instruction to start/resume contribution but contribution is already in progress...[skipping]`
719
+ )
720
+ return
721
+ }
714
722
  // Communicate resume / start of the contribution to participant.
715
723
  await simpleLoader(
716
724
  `${
@@ -720,18 +728,24 @@ export const listenToParticipantDocumentChanges = async (
720
728
  3000
721
729
  )
722
730
 
723
- // Start / Resume the contribution for the participant.
724
- await handleStartOrResumeContribution(
725
- cloudFunctions,
726
- firestoreDatabase,
727
- ceremony,
728
- circuit,
729
- participant,
730
- entropy,
731
- providerUserId,
732
- false, // not finalizing.
733
- circuits.length
734
- )
731
+ try {
732
+ contributionInProgress = true
733
+
734
+ // Start / Resume the contribution for the participant.
735
+ await handleStartOrResumeContribution(
736
+ cloudFunctions,
737
+ firestoreDatabase,
738
+ ceremony,
739
+ circuit,
740
+ participant,
741
+ entropy,
742
+ providerUserId,
743
+ false, // not finalizing.
744
+ circuits.length
745
+ )
746
+ } finally {
747
+ contributionInProgress = false
748
+ }
735
749
  }
736
750
  // Scenario (3.A).
737
751
  else if (isWaitingForContribution)
@@ -943,7 +957,11 @@ const contribute = async (opt: any) => {
943
957
  } else selectedCeremony = selectedCeremonyDocument.at(0)
944
958
  } else {
945
959
  // Prompt the user to select a ceremony from the opened ones.
946
- selectedCeremony = await promptForCeremonySelection(ceremoniesOpenedForContributions, false)
960
+ selectedCeremony = await promptForCeremonySelection(
961
+ ceremoniesOpenedForContributions,
962
+ false,
963
+ "Which ceremony would you like to contribute to?"
964
+ )
947
965
  }
948
966
 
949
967
  // Get selected ceremony circuit(s) documents.
@@ -269,7 +269,11 @@ const finalize = async (opt: any) => {
269
269
  )
270
270
 
271
271
  // Prompt for ceremony selection.
272
- const selectedCeremony = await promptForCeremonySelection(ceremoniesClosedForFinalization, true)
272
+ const selectedCeremony = await promptForCeremonySelection(
273
+ ceremoniesClosedForFinalization,
274
+ true,
275
+ "Which ceremony would you like to finalize?"
276
+ )
273
277
 
274
278
  // Get coordinator participant document.
275
279
  let participant = await getDocumentById(
@@ -1,6 +1,7 @@
1
1
  export { default as setup } from "./setup.js"
2
2
  export { default as auth } from "./auth.js"
3
3
  export { default as authBandada } from "./authBandada.js"
4
+ export { default as authSIWE } from "./authSIWE.js"
4
5
  export { default as contribute } from "./contribute.js"
5
6
  export { default as observe } from "./observe.js"
6
7
  export { default as finalize } from "./finalize.js"
@@ -6,7 +6,7 @@ import { showError } from "../lib/errors.js"
6
6
  import { askForConfirmation } from "../lib/prompts.js"
7
7
  import { customSpinner, sleep, terminate } from "../lib/utils.js"
8
8
  import theme from "../lib/theme.js"
9
- import { deleteLocalAccessToken, deleteLocalBandadaIdentity } from "../lib/localConfigs.js"
9
+ import { deleteLocalAccessToken, deleteLocalAuthMethod, deleteLocalBandadaIdentity } from "../lib/localConfigs.js"
10
10
 
11
11
  /**
12
12
  * Logout command.
@@ -52,6 +52,7 @@ const logout = async () => {
52
52
  await signOut(auth)
53
53
 
54
54
  // Delete local token.
55
+ deleteLocalAuthMethod()
55
56
  deleteLocalAccessToken()
56
57
  deleteLocalBandadaIdentity()
57
58
 
@@ -147,7 +147,11 @@ const observe = async () => {
147
147
  const runningCeremoniesDocs = await getOpenedCeremonies(firestoreDatabase)
148
148
 
149
149
  // Ask to select a ceremony.
150
- const ceremony = await promptForCeremonySelection(runningCeremoniesDocs, false)
150
+ const ceremony = await promptForCeremonySelection(
151
+ runningCeremoniesDocs,
152
+ false,
153
+ "Which ceremony would you like to observe?"
154
+ )
151
155
 
152
156
  console.log(`${logSymbols.info} Refresh rate set to ~3 seconds for waiting queue updates\n`)
153
157
 
@@ -271,7 +271,7 @@ export const displayCeremonySummary = (ceremonyInputData: CeremonyInputData, cir
271
271
 
272
272
  /**
273
273
  * Check if the smallest Powers of Tau has already been downloaded/stored in the correspondent local path
274
- * @dev we are downloading the Powers of Tau file from Hermez Cryptography Phase 1 Trusted Setup.
274
+ * @dev we are downloading the Powers of Tau file from Perpetual Powers of Tau Phase 1 Trusted Setup.
275
275
  * @param powers <string> - the smallest amount of powers needed for the given circuit (should be in a 'XY' stringified form).
276
276
  * @param ptauCompleteFilename <string> - the complete file name of the powers of tau file to be downloaded.
277
277
  * @returns <Promise<void>>
@@ -293,7 +293,7 @@ export const checkAndDownloadSmallestPowersOfTau = async (
293
293
  const spinner = customSpinner(
294
294
  `Downloading the ${theme.text.bold(
295
295
  `#${powers}`
296
- )} smallest PoT file needed from the Hermez Cryptography Phase 1 Trusted Setup...`,
296
+ )} smallest PoT file needed from the Perpetual Powers of Tau Phase 1 Trusted Setup...`,
297
297
  `clock`
298
298
  )
299
299
  spinner.start()
@@ -463,7 +463,7 @@ export const handleCircuitArtifactUploadToStorage = async (
463
463
  * @notice The setup command allows the coordinator of the ceremony to prepare the next ceremony by interacting with the CLI.
464
464
  * @dev For proper execution, the command must be run in a folder containing the R1CS files related to the circuits
465
465
  * for which the coordinator wants to create the ceremony. The command will download the necessary Tau powers
466
- * from Hermez's ceremony Phase 1 Reliable Setup Ceremony.
466
+ * from PPoT ceremony Phase 1 Setup Ceremony.
467
467
  * @param cmd? <any> - the path to the ceremony setup file.
468
468
  */
469
469
  const setup = async (cmd: { template?: string; auth?: string }) => {
package/src/index.ts CHANGED
@@ -7,6 +7,7 @@ import { fileURLToPath } from "url"
7
7
  import {
8
8
  setup,
9
9
  auth,
10
+ authSIWE,
10
11
  authBandada,
11
12
  contribute,
12
13
  observe,
@@ -32,6 +33,10 @@ program
32
33
  .command("auth-bandada")
33
34
  .description("authenticate yourself in a privacy-perserving manner using Bandada")
34
35
  .action(authBandada)
36
+ program
37
+ .command("auth-siwe")
38
+ .description("authenticate yourself using your Ethereum account (Sign In With Ethereum - SIWE)")
39
+ .action(authSIWE)
35
40
  program
36
41
  .command("contribute")
37
42
  .description("compute contributions for a Phase2 Trusted Setup ceremony circuits")
package/src/lib/errors.ts CHANGED
@@ -34,7 +34,7 @@ export const COMMAND_ERRORS = {
34
34
  COMMAND_SETUP_NO_R1CS: `Unable to retrieve R1CS files from current working directory. Please, run this command from a working directory where the R1CS files are located to continue with the setup process. We kindly ask you to run the command from an empty directory containing only the R1CS and WASM files.`,
35
35
  COMMAND_SETUP_NO_WASM: `Unable to retrieve WASM files from current working directory. Please, run this command from a working directory where the WASM files are located to continue with the setup process. We kindly ask you to run the command from an empty directory containing only the WASM and R1CS files.`,
36
36
  COMMAND_SETUP_MISMATCH_R1CS_WASM: `The folder contains more R1CS files than WASM files (or vice versa). Please, run this command from a working directory where each R1CS is paired with its corresponding file WASM.`,
37
- COMMAND_SETUP_DOWNLOAD_PTAU: `Unable to download Powers of Tau file from Hermez Cryptography Phase 1 Trusted Setup. Possible causes may involve an error while making the request (be sure to have a stable internet connection). Please, we kindly ask you to terminate the current session and repeat the process.`,
37
+ COMMAND_SETUP_DOWNLOAD_PTAU: `Unable to download Powers of Tau file from PPoT Phase 1 Trusted Setup. Possible causes may involve an error while making the request (be sure to have a stable internet connection). Please, we kindly ask you to terminate the current session and repeat the process.`,
38
38
  COMMAND_SETUP_ABORT: `You chose to abort the setup process.`,
39
39
  COMMAND_CONTRIBUTE_NO_OPENED_CEREMONIES: `Unfortunately, there is no ceremony for which you can make a contribution at this time. Please, try again later.`,
40
40
  COMMAND_CONTRIBUTE_NO_PARTICIPANT_DATA: `Unable to retrieve your data as ceremony participant. Please, terminate the current session and try again later. If the error persists, please contact the ceremony coordinator.`,
@@ -28,6 +28,10 @@ const config = new Conf({
28
28
  bandadaIdentity: {
29
29
  type: "string",
30
30
  default: ""
31
+ },
32
+ authMethod: {
33
+ type: "string",
34
+ default: ""
31
35
  }
32
36
  }
33
37
  })
@@ -118,6 +122,29 @@ export const setLocalBandadaIdentity = (identity: string) => config.set("bandada
118
122
  */
119
123
  export const deleteLocalBandadaIdentity = () => config.delete("bandadaIdentity")
120
124
 
125
+ /**
126
+ * Return the authentication method, if present.
127
+ * @returns <string | undefined> - the authentication method if present, otherwise undefined.
128
+ */
129
+ export const getLocalAuthMethod = (): string | unknown => config.get("authMethod")
130
+
131
+ /**
132
+ * Check if the authentication method exists in the local storage.
133
+ * @returns <boolean>
134
+ */
135
+ export const checkLocalAuthMethod = (): boolean => config.has("authMethod") && !!config.get("authMethod")
136
+
137
+ /**
138
+ * Set the authentication method.
139
+ * @param method <string> - the authentication method to be stored.
140
+ */
141
+ export const setLocalAuthMethod = (method: string) => config.set("authMethod", method)
142
+
143
+ /**
144
+ * Delete the stored authentication method.
145
+ */
146
+ export const deleteLocalAuthMethod = () => config.delete("authMethod")
147
+
121
148
  /**
122
149
  * Get the complete local file path.
123
150
  * @param cwd <string> - the current working directory path.
@@ -661,7 +661,8 @@ export const promptPotSelector = async (options: Array<string>): Promise<string>
661
661
  */
662
662
  export const promptForCeremonySelection = async (
663
663
  ceremoniesDocuments: Array<FirebaseDocumentInfo>,
664
- isFinalizing: boolean
664
+ isFinalizing: boolean,
665
+ messageToDisplay?: string
665
666
  ): Promise<FirebaseDocumentInfo> => {
666
667
  // Prepare state.
667
668
  const choices: Array<Choice> = []
@@ -686,11 +687,7 @@ export const promptForCeremonySelection = async (
686
687
  const { ceremony } = await prompts({
687
688
  type: "select",
688
689
  name: "ceremony",
689
- message: theme.text.bold(
690
- !isFinalizing
691
- ? "Which ceremony would you like to contribute to?"
692
- : "Which ceremony would you like to finalize?"
693
- ),
690
+ message: theme.text.bold(messageToDisplay),
694
691
  choices,
695
692
  initial: 0
696
693
  })
@@ -14,9 +14,9 @@ import { AuthUser } from "../types/index.js"
14
14
  import { CONFIG_ERRORS, CORE_SERVICES_ERRORS, showError, THIRD_PARTY_SERVICES_ERRORS } from "./errors.js"
15
15
  import {
16
16
  checkLocalAccessToken,
17
- checkLocalBandadaIdentity,
18
17
  deleteLocalAccessToken,
19
- getLocalAccessToken
18
+ getLocalAccessToken,
19
+ getLocalAuthMethod
20
20
  } from "./localConfigs.js"
21
21
  import theme from "./theme.js"
22
22
  import { exchangeGithubTokenForCredentials, getGithubProviderUserId, getUserHandleFromProviderUserId } from "./utils.js"
@@ -171,21 +171,33 @@ export const checkAuth = async (firebaseApp: FirebaseApp): Promise<AuthUser> =>
171
171
 
172
172
  let providerUserId: string
173
173
  let username: string
174
- const isLocalBandadaIdentityStored = checkLocalBandadaIdentity()
175
- if (isLocalBandadaIdentityStored) {
176
- const userCredentials = await signInWithCustomToken(getAuth(), token)
177
- providerUserId = userCredentials.user.uid
178
- username = providerUserId
179
- } else {
180
- // Get credentials.
181
- const credentials = exchangeGithubTokenForCredentials(token)
182
-
183
- // Sign in to Firebase using credentials.
184
- await signInToFirebase(firebaseApp, credentials)
185
-
186
- // Get Github unique identifier (handle-id).
187
- providerUserId = await getGithubProviderUserId(String(token))
188
- username = getUserHandleFromProviderUserId(providerUserId)
174
+ const authMethod = getLocalAuthMethod()
175
+ switch (authMethod) {
176
+ case "github": {
177
+ // Get credentials.
178
+ const credentials = exchangeGithubTokenForCredentials(token)
179
+ // Sign in to Firebase using credentials.
180
+ await signInToFirebase(firebaseApp, credentials)
181
+ // Get Github unique identifier (handle-id).
182
+ providerUserId = await getGithubProviderUserId(String(token))
183
+ username = getUserHandleFromProviderUserId(providerUserId)
184
+ break
185
+ }
186
+ case "bandada": {
187
+ const userCredentials = await signInWithCustomToken(getAuth(), token)
188
+ providerUserId = userCredentials.user.uid
189
+ username = providerUserId
190
+ break
191
+ }
192
+ case "siwe": {
193
+ const userCredentials = await signInWithCustomToken(getAuth(), token)
194
+ providerUserId = userCredentials.user.uid
195
+ username = providerUserId
196
+ break
197
+ }
198
+ default: {
199
+ break
200
+ }
189
201
  }
190
202
 
191
203
  // Get current authenticated user.
package/src/lib/utils.ts CHANGED
@@ -18,7 +18,8 @@ import {
18
18
  ParticipantContributionStep,
19
19
  permanentlyStoreCurrentContributionTimeAndHash,
20
20
  progressToNextContributionStep,
21
- verifyContribution
21
+ verifyContribution,
22
+ contribHashRegex
22
23
  } from "@devtion/actions"
23
24
  import { Presets, SingleBar } from "cli-progress"
24
25
  import dotenv from "dotenv"
@@ -159,7 +160,7 @@ export const getUserHandleFromProviderUserId = (providerUserId: string): string
159
160
  return providerUserId
160
161
  }
161
162
 
162
- return providerUserId.split("-")[0]
163
+ return providerUserId.substring(0, providerUserId.lastIndexOf("-"))
163
164
  }
164
165
 
165
166
  /**
@@ -664,7 +665,7 @@ export const handleStartOrResumeContribution = async (
664
665
 
665
666
  // Read local transcript file info to get the contribution hash.
666
667
  const transcriptContents = readFile(transcriptLocalFilePath)
667
- const matchContributionHash = transcriptContents.match(/Contribution.+Hash.+\n\t\t.+\n\t\t.+\n.+\n\t\t.+\n/)
668
+ const matchContributionHash = transcriptContents.match(contribHashRegex)
668
669
 
669
670
  if (!matchContributionHash)
670
671
  showError(COMMAND_ERRORS.COMMAND_CONTRIBUTE_FINALIZE_NO_TRANSCRIPT_CONTRIBUTION_HASH_MATCH, true)
@@ -81,3 +81,65 @@ export type VerifiedBandadaResponse = {
81
81
  message: string
82
82
  token: string
83
83
  }
84
+
85
+ /**
86
+ * Define the return object of the device code uri request.
87
+ * @typedef {Object} OAuthDeviceCodeResponse
88
+ * @property {string} device_code - the device code.
89
+ * @property {string} user_code - the user code.
90
+ * @property {string} verification_uri - the verification uri.
91
+ * @property {number} expires_in - the expiration time in seconds.
92
+ * @property {number} interval - the interval time in seconds.
93
+ * @property {string} verification_uri_complete - the complete verification uri.
94
+ * @property {string} error - in case there was an error, show the code
95
+ * @property {string} error_description - error details.
96
+ * @property {string} error_uri - error uri.
97
+ */
98
+ export type OAuthDeviceCodeResponse = {
99
+ device_code: string
100
+ user_code: string
101
+ verification_uri: string
102
+ expires_in: number
103
+ interval: number
104
+ verification_uri_complete: string
105
+ // error response should contain
106
+ error?: string
107
+ error_description?: string
108
+ error_uri?: string
109
+ }
110
+
111
+ /**
112
+ * Define the return object of the polling endpoint
113
+ * @typedef {Object} OAuthTokenResponse
114
+ * @property {string} access_token - the resulting device flow token
115
+ * @property {string} token_type - token type
116
+ * @property {number} expires_in - when does the token expires
117
+ * @property {string} scope - the scope requested by the initial device flow endpoint
118
+ * @property {string} refresh_token - refresh token
119
+ * @property {string} id_token - id token
120
+ * @property {string} error - in case there was an error, show the code
121
+ * @property {string} error_description - error details
122
+ */
123
+ export type OAuthTokenResponse = {
124
+ access_token: string
125
+ token_type: string
126
+ expires_in: number
127
+ scope: string
128
+ refresh_token: string
129
+ id_token: string
130
+ // error response should contain
131
+ error?: string
132
+ error_description?: string
133
+ }
134
+
135
+ /**
136
+ * @typedef {Object} CheckNonceOfSIWEAddressResponse
137
+ * @property {boolean} valid - if the checking was valid or not
138
+ * @property {string} message - more information about the validity
139
+ * @property {string} token - token to sign into Firebase
140
+ */
141
+ export type CheckNonceOfSIWEAddressResponse = {
142
+ valid: boolean
143
+ message: string
144
+ token: string
145
+ }