diff --git a/apps/remix-ide/src/app/tabs/locales/en/filePanel.json b/apps/remix-ide/src/app/tabs/locales/en/filePanel.json index 0554b0ffa7..6b7dc748e5 100644 --- a/apps/remix-ide/src/app/tabs/locales/en/filePanel.json +++ b/apps/remix-ide/src/app/tabs/locales/en/filePanel.json @@ -103,6 +103,7 @@ "filePanel.mintable": "Mintable", "filePanel.burnable": "Burnable", "filePanel.pausable": "Pausable", + "filePanel.semaphore": "Semaphore", "filePanel.transparent": "Transparent", "filePanel.initGitRepoTitle": "Check option to initialize workspace as a new git repository", "filePanel.switchToBranchTitle1": "Checkout new branch from remote branch", diff --git a/apps/remix-ide/src/remixAppManager.js b/apps/remix-ide/src/remixAppManager.js index a6e4565ac6..9f4a70e017 100644 --- a/apps/remix-ide/src/remixAppManager.js +++ b/apps/remix-ide/src/remixAppManager.js @@ -74,8 +74,7 @@ const requiredModules = [ 'compilationDetails', 'contractflattener', 'solidity-script', - 'openaigpt', - 'circuit-compiler' + 'openaigpt' ] // dependentModules shouldn't be manually activated (e.g hardhat is activated by remixd) @@ -114,7 +113,8 @@ export function isNative(name) { 'injected-ephemery-testnet-provider', 'injected', 'doc-gen', - 'doc-viewer' + 'doc-viewer', + 'circuit-compiler' ] return nativePlugins.includes(name) || requiredModules.includes(name) } diff --git a/libs/remix-ui/workspace/src/lib/actions/events.ts b/libs/remix-ui/workspace/src/lib/actions/events.ts index 5c9b03141c..3c85231cb6 100644 --- a/libs/remix-ui/workspace/src/lib/actions/events.ts +++ b/libs/remix-ui/workspace/src/lib/actions/events.ts @@ -1,7 +1,7 @@ import { fileDecoration } from '@remix-ui/file-decorators' import { extractParentFromKey } from '@remix-ui/helper' import React from 'react' -import { action, WorkspaceTemplate } from '../types' +import { action, FileTree, WorkspaceTemplate } from '../types' import { ROOT_PATH } from '../utils/constants' import { displayNotification, displayPopUp, fileAddedSuccess, fileRemovedSuccess, fileRenamedSuccess, folderAddedSuccess, loadLocalhostError, loadLocalhostRequest, loadLocalhostSuccess, removeContextMenuItem, removeFocus, rootFolderChangedSuccess, setContextMenuItem, setMode, setReadOnlyMode, setFileDecorationSuccess } from './payload' import { addInputField, createWorkspace, deleteWorkspace, fetchWorkspaceDirectory, renameWorkspace, switchToWorkspace, uploadFile } from './workspace' @@ -173,8 +173,8 @@ const folderAdded = async (folderPath: string) => { const provider = plugin.fileManager.currentFileProvider() const path = extractParentFromKey(folderPath) || ROOT_PATH - const promise = new Promise((resolve) => { - provider.resolveDirectory(path, (error, fileTree) => { + const promise: Promise = new Promise((resolve) => { + provider.resolveDirectory(path, (error, fileTree: FileTree) => { if (error) console.error(error) resolve(fileTree) }) @@ -196,8 +196,8 @@ const fileRemoved = async (removePath: string) => { const fileRenamed = async (oldPath: string) => { const provider = plugin.fileManager.currentFileProvider() const path = extractParentFromKey(oldPath) || ROOT_PATH - const promise = new Promise((resolve) => { - provider.resolveDirectory(path, (error, fileTree) => { + const promise: Promise = new Promise((resolve) => { + provider.resolveDirectory(path, (error, fileTree: FileTree) => { if (error) console.error(error) resolve(fileTree) diff --git a/libs/remix-ui/workspace/src/lib/actions/index.ts b/libs/remix-ui/workspace/src/lib/actions/index.ts index 9b8691319d..82f71e40f4 100644 --- a/libs/remix-ui/workspace/src/lib/actions/index.ts +++ b/libs/remix-ui/workspace/src/lib/actions/index.ts @@ -9,6 +9,7 @@ import { QueryParams } from '@remix-project/remix-lib' import { fetchContractFromEtherscan } from '@remix-project/core-plugin' // eslint-disable-line import JSZip from 'jszip' import isElectron from 'is-electron' +import { Actions, FileTree } from '../types' export * from './events' export * from './workspace' @@ -16,7 +17,7 @@ export * from './workspace' const queryParams = new QueryParams() const _paq = window._paq = window._paq || [] -let plugin, dispatch: React.Dispatch +let plugin, dispatch: React.Dispatch export type UrlParametersType = { gist: string, @@ -43,7 +44,7 @@ const basicWorkspaceInit = async (workspaces: { name: string; isGitRepo: boolean } } -export const initWorkspace = (filePanelPlugin) => async (reducerDispatch: React.Dispatch) => { +export const initWorkspace = (filePanelPlugin) => async (reducerDispatch: React.Dispatch) => { if (filePanelPlugin) { plugin = filePanelPlugin dispatch = reducerDispatch @@ -144,18 +145,18 @@ export const initWorkspace = (filePanelPlugin) => async (reducerDispatch: React. export const fetchDirectory = async (path: string) => { const provider = plugin.fileManager.currentFileProvider() const promise = new Promise((resolve) => { - provider.resolveDirectory(path, (error, fileTree) => { + provider.resolveDirectory(path, (error, fileTree: FileTree) => { if (error) console.error(error) resolve(fileTree) }) }) - dispatch(fetchDirectoryRequest(promise)) - promise.then((fileTree) => { + dispatch(fetchDirectoryRequest()) + promise.then((fileTree: FileTree) => { dispatch(fetchDirectorySuccess(path, fileTree)) - }).catch((error) => { - dispatch(fetchDirectoryError({ error })) + }).catch((error: ErrorEvent) => { + dispatch(fetchDirectoryError(error.message)) }) return promise } diff --git a/libs/remix-ui/workspace/src/lib/actions/payload.ts b/libs/remix-ui/workspace/src/lib/actions/payload.ts index ea804ce2d1..6f5d5793cb 100644 --- a/libs/remix-ui/workspace/src/lib/actions/payload.ts +++ b/libs/remix-ui/workspace/src/lib/actions/payload.ts @@ -1,292 +1,300 @@ import { fileDecoration } from '@remix-ui/file-decorators' -import { action } from '../types' +import { Action, ActionPayloadTypes, FileTree, WorkspaceElement, action } from '../types' -export const setCurrentWorkspace = (workspace: { name: string; isGitRepo: boolean; }) => { +export const setCurrentWorkspace = (workspace: { name: string; isGitRepo: boolean; }): Action<'SET_CURRENT_WORKSPACE'> => { return { type: 'SET_CURRENT_WORKSPACE', payload: workspace } } -export const setWorkspaces = (workspaces: { name: string; isGitRepo: boolean; }[]) => { +export const setWorkspaces = (workspaces: { name: string; isGitRepo: boolean; }[]): Action<'SET_WORKSPACES'> => { return { type: 'SET_WORKSPACES', payload: workspaces } } -export const setMode = (mode: 'browser' | 'localhost') => { +export const setMode = (mode: 'browser' | 'localhost'): Action<'SET_MODE'> => { return { type: 'SET_MODE', payload: mode } } -export const fetchDirectoryError = (error: any) => { +export const fetchDirectoryError = (error: string): Action<'FETCH_DIRECTORY_ERROR'> => { return { type: 'FETCH_DIRECTORY_ERROR', payload: error } } -export const fetchDirectoryRequest = (promise: Promise) => { +export const fetchDirectoryRequest = (): Action<'FETCH_DIRECTORY_REQUEST'> => { return { type: 'FETCH_DIRECTORY_REQUEST', - payload: promise + payload: undefined } } -export const fetchDirectorySuccess = (path: string, fileTree) => { +export const fetchDirectorySuccess = (path: string, fileTree: FileTree): Action<'FETCH_DIRECTORY_SUCCESS'> => { return { type: 'FETCH_DIRECTORY_SUCCESS', payload: { path, fileTree } } } -export const displayNotification = (title: string, message: string, labelOk: string, labelCancel: string, actionOk?: (...args) => void, actionCancel?: (...args) => void) => { +export const displayNotification = (title: string, message: string, labelOk: string, labelCancel: string, actionOk?: (...args) => void, actionCancel?: (...args) => void): Action<'DISPLAY_NOTIFICATION'> => { return { type: 'DISPLAY_NOTIFICATION', payload: { title, message, labelOk, labelCancel, actionOk, actionCancel } } } -export const hideNotification = () => { +export const hideNotification = (): Action<'HIDE_NOTIFICATION'> => { return { - type: 'HIDE_NOTIFICATION' + type: 'HIDE_NOTIFICATION', + payload: null } } -export const fileAddedSuccess = (filePath: string) => { +export const fileAddedSuccess = (filePath: string): Action<'FILE_ADDED_SUCCESS'> => { return { type: 'FILE_ADDED_SUCCESS', payload: filePath } } -export const folderAddedSuccess = (path: string, folderPath: string, fileTree) => { +export const folderAddedSuccess = (path: string, folderPath: string, fileTree: FileTree): Action<'FOLDER_ADDED_SUCCESS'> => { return { type: 'FOLDER_ADDED_SUCCESS', payload: { path, folderPath, fileTree } } } -export const fileRemovedSuccess = (removePath: string) => { +export const fileRemovedSuccess = (removePath: string): Action<'FILE_REMOVED_SUCCESS'> => { return { type: 'FILE_REMOVED_SUCCESS', payload: removePath } } -export const fileRenamedSuccess = (path: string, oldPath: string, fileTree) => { +export const fileRenamedSuccess = (path: string, oldPath: string, fileTree: FileTree): Action<'FILE_RENAMED_SUCCESS'> => { return { type: 'FILE_RENAMED_SUCCESS', payload: { path, oldPath, fileTree } } } -export const rootFolderChangedSuccess = (path: string) => { +export const rootFolderChangedSuccess = (path: string): Action<'ROOT_FOLDER_CHANGED'> => { return { type: 'ROOT_FOLDER_CHANGED', payload: path } } -export const addInputFieldSuccess = (path: string, fileTree, type: 'file' | 'folder' | 'gist') => { +export const addInputFieldSuccess = (path: string, fileTree: FileTree, type: 'file' | 'folder'): Action<'ADD_INPUT_FIELD'> => { return { type: 'ADD_INPUT_FIELD', payload: { path, fileTree, type } } } -export const removeInputFieldSuccess = (path: string) => { +export const removeInputFieldSuccess = (path: string): Action<'REMOVE_INPUT_FIELD'> => { return { type: 'REMOVE_INPUT_FIELD', payload: { path } } } -export const setReadOnlyMode = (mode: boolean) => { +export const setReadOnlyMode = (mode: boolean): Action<'SET_READ_ONLY_MODE'> => { return { type: 'SET_READ_ONLY_MODE', payload: mode } } -export const createWorkspaceError = (error: any) => { +export const createWorkspaceError = (error: string): Action<'CREATE_WORKSPACE_ERROR'> => { return { type: 'CREATE_WORKSPACE_ERROR', payload: error } } -export const createWorkspaceRequest = (promise: Promise) => { +export const createWorkspaceRequest = (): Action<'CREATE_WORKSPACE_REQUEST'> => { return { type: 'CREATE_WORKSPACE_REQUEST', - payload: promise + payload: null } } -export const createWorkspaceSuccess = (workspaceName: { name: string; isGitRepo: boolean; branches?: { remote: any; name: string; }[], currentBranch?: string }) => { +export const createWorkspaceSuccess = (workspaceName: ActionPayloadTypes['CREATE_WORKSPACE_SUCCESS']): Action<'CREATE_WORKSPACE_SUCCESS'> => { return { type: 'CREATE_WORKSPACE_SUCCESS', payload: workspaceName } } -export const fetchWorkspaceDirectoryError = (error: any) => { +export const fetchWorkspaceDirectoryError = (error: string): Action<'FETCH_WORKSPACE_DIRECTORY_ERROR'> => { return { type: 'FETCH_WORKSPACE_DIRECTORY_ERROR', payload: error } } -export const fetchWorkspaceDirectoryRequest = (promise: Promise) => { +export const fetchWorkspaceDirectoryRequest = (): Action<'FETCH_WORKSPACE_DIRECTORY_REQUEST'> => { return { type: 'FETCH_WORKSPACE_DIRECTORY_REQUEST', - payload: promise + payload: null } } -export const fetchWorkspaceDirectorySuccess = (path: string, fileTree) => { +export const fetchWorkspaceDirectorySuccess = (path: string, fileTree: FileTree): Action<'FETCH_WORKSPACE_DIRECTORY_SUCCESS'> => { return { type: 'FETCH_WORKSPACE_DIRECTORY_SUCCESS', payload: { path, fileTree } } } -export const setRenameWorkspace = (oldName: string, workspaceName: string) => { +export const setRenameWorkspace = (oldName: string, workspaceName: string): Action<'RENAME_WORKSPACE'> => { return { type: 'RENAME_WORKSPACE', payload: { oldName, workspaceName } } } -export const setDeleteWorkspace = (workspaceName: string) => { +export const setDeleteWorkspace = (workspaceName: string): Action<'DELETE_WORKSPACE'> => { return { type: 'DELETE_WORKSPACE', payload: workspaceName } } -export const displayPopUp = (message: string) => { +export const displayPopUp = (message: string): Action<'DISPLAY_POPUP_MESSAGE'> => { return { type: 'DISPLAY_POPUP_MESSAGE', payload: message } } -export const hidePopUp = () => { +export const hidePopUp = (): Action<'HIDE_POPUP_MESSAGE'> => { return { - type: 'HIDE_POPUP_MESSAGE' + type: 'HIDE_POPUP_MESSAGE', + payload: null } } -export const focusElement = (elements: { key: string, type: 'file' | 'folder' | 'gist' }[]) => { +export const focusElement = (elements: { key: string, type: WorkspaceElement }[]): Action<'SET_FOCUS_ELEMENT'> => { return { type: 'SET_FOCUS_ELEMENT', payload: elements } } -export const removeFocus = (name: string) => { +export const removeFocus = (name: string): Action<'REMOVE_FOCUS_ELEMENT'> => { return { type: 'REMOVE_FOCUS_ELEMENT', payload: name } } -export const setContextMenuItem = (item: action) => { +export const setContextMenuItem = (item: action): Action<'SET_CONTEXT_MENU_ITEM'> => { return { type: 'SET_CONTEXT_MENU_ITEM', payload: item } } -export const removeContextMenuItem = (plugin) => { +export const removeContextMenuItem = (plugin: { name: string }): Action<'REMOVE_CONTEXT_MENU_ITEM'> => { return { type: 'REMOVE_CONTEXT_MENU_ITEM', payload: plugin } } -export const setExpandPath = (paths: string[]) => { +export const setExpandPath = (paths: string[]): Action<'SET_EXPAND_PATH'> => { return { type: 'SET_EXPAND_PATH', payload: paths } } -export const loadLocalhostError = (error: any) => { +export const loadLocalhostError = (error: string): Action<'LOAD_LOCALHOST_ERROR'> => { return { type: 'LOAD_LOCALHOST_ERROR', payload: error } } -export const loadLocalhostRequest = () => { +export const loadLocalhostRequest = (): Action<'LOAD_LOCALHOST_REQUEST'> => { return { - type: 'LOAD_LOCALHOST_REQUEST' + type: 'LOAD_LOCALHOST_REQUEST', + payload: null } } -export const loadLocalhostSuccess = () => { +export const loadLocalhostSuccess = (): Action<'LOAD_LOCALHOST_SUCCESS'> => { return { - type: 'LOAD_LOCALHOST_SUCCESS' + type: 'LOAD_LOCALHOST_SUCCESS', + payload: null } } -export const fsInitializationCompleted = () => { +export const fsInitializationCompleted = (): Action<'FS_INITIALIZATION_COMPLETED'> => { return { - type: 'FS_INITIALIZATION_COMPLETED' + type: 'FS_INITIALIZATION_COMPLETED', + payload: null } } -export const setFileDecorationSuccess = (items: fileDecoration[]) => { +export const setFileDecorationSuccess = (items: fileDecoration[]): Action<'SET_FILE_DECORATION_SUCCESS'> => { return { type: 'SET_FILE_DECORATION_SUCCESS', payload: items } } -export const cloneRepositoryRequest = () => { +export const cloneRepositoryRequest = (): Action<'CLONE_REPOSITORY_REQUEST'> => { return { - type: 'CLONE_REPOSITORY_REQUEST' + type: 'CLONE_REPOSITORY_REQUEST', + payload: null } } -export const cloneRepositorySuccess = () => { +export const cloneRepositorySuccess = (): Action<'CLONE_REPOSITORY_SUCCESS'> => { return { - type: 'CLONE_REPOSITORY_SUCCESS' + type: 'CLONE_REPOSITORY_SUCCESS', + payload: null } } -export const cloneRepositoryFailed = () => { +export const cloneRepositoryFailed = (): Action<'CLONE_REPOSITORY_FAILED'> => { return { - type: 'CLONE_REPOSITORY_FAILED' + type: 'CLONE_REPOSITORY_FAILED', + payload: null } } -export const setCurrentWorkspaceBranches = (branches?: { remote: any, name: string }[]) => { +export const setCurrentWorkspaceBranches = (branches?: { remote: any, name: string }[]): Action<'SET_CURRENT_WORKSPACE_BRANCHES'> => { return { type: 'SET_CURRENT_WORKSPACE_BRANCHES', payload: branches } } -export const setCurrentWorkspaceCurrentBranch = (currentBranch?: string) => { +export const setCurrentWorkspaceCurrentBranch = (currentBranch?: string): Action<'SET_CURRENT_WORKSPACE_CURRENT_BRANCH'> => { return { type: 'SET_CURRENT_WORKSPACE_CURRENT_BRANCH', payload: currentBranch } } -export const setCurrentWorkspaceIsGitRepo = (isRepo: boolean) => { +export const setCurrentWorkspaceIsGitRepo = (isRepo: boolean): Action<'SET_CURRENT_WORKSPACE_IS_GITREPO'> => { return { type: 'SET_CURRENT_WORKSPACE_IS_GITREPO', payload: isRepo } } -export const setGitConfig = (config: {username: string, token: string, email: string}) => { +export const setGitConfig = (config: {username: string, token: string, email: string}): Action<'SET_GIT_CONFIG'> => { return { type: 'SET_GIT_CONFIG', payload: config diff --git a/libs/remix-ui/workspace/src/lib/actions/workspace.ts b/libs/remix-ui/workspace/src/lib/actions/workspace.ts index 37652f1b04..e425315711 100644 --- a/libs/remix-ui/workspace/src/lib/actions/workspace.ts +++ b/libs/remix-ui/workspace/src/lib/actions/workspace.ts @@ -5,7 +5,7 @@ import axios, { AxiosResponse } from 'axios' import { addInputFieldSuccess, cloneRepositoryFailed, cloneRepositoryRequest, cloneRepositorySuccess, createWorkspaceError, createWorkspaceRequest, createWorkspaceSuccess, displayNotification, displayPopUp, fetchWorkspaceDirectoryError, fetchWorkspaceDirectoryRequest, fetchWorkspaceDirectorySuccess, hideNotification, setCurrentWorkspace, setCurrentWorkspaceBranches, setCurrentWorkspaceCurrentBranch, setDeleteWorkspace, setMode, setReadOnlyMode, setRenameWorkspace, setCurrentWorkspaceIsGitRepo, setGitConfig } from './payload' import { addSlash, checkSlash, checkSpecialChars } from '@remix-ui/helper' -import { JSONStandardInput, WorkspaceTemplate } from '../types' +import { FileTree, JSONStandardInput, WorkspaceTemplate } from '../types' import { QueryParams } from '@remix-project/remix-lib' import * as templateWithContent from '@remix-project/remix-ws-templates' import { ROOT_PATH, slitherYml, solTestYml, tsSolTestYml } from '../utils/constants' @@ -58,8 +58,8 @@ export const setPlugin = (filePanelPlugin, reducerDispatch) => { export const addInputField = async (type: 'file' | 'folder', path: string, cb?: (err: Error, result?: string | number | boolean | Record) => void) => { const provider = plugin.fileManager.currentFileProvider() - const promise = new Promise((resolve, reject) => { - provider.resolveDirectory(path, (error, fileTree) => { + const promise: Promise = new Promise((resolve, reject) => { + provider.resolveDirectory(path, (error, fileTree: FileTree) => { if (error) { cb && cb(error) return reject(error) @@ -85,7 +85,7 @@ const removeSlash = (s: string) => { export const createWorkspace = async (workspaceName: string, workspaceTemplateName: WorkspaceTemplate, opts = null, isEmpty = false, cb?: (err: Error, result?: string | number | boolean | Record) => void, isGitRepo: boolean = false, createCommit: boolean = true) => { await plugin.fileManager.closeAllFiles() const promise = createWorkspaceTemplate(workspaceName, workspaceTemplateName) - dispatch(createWorkspaceRequest(promise)) + dispatch(createWorkspaceRequest()) promise.then(async () => { dispatch(createWorkspaceSuccess({ name: workspaceName, isGitRepo })) await plugin.setWorkspace({ name: workspaceName, isLocalhost: false }) @@ -136,10 +136,15 @@ export const createWorkspace = async (workspaceName: string, workspaceTemplateNa const isActive = await plugin.call('manager', 'isActive', 'dgit') if (!isActive) await plugin.call('manager', 'activatePlugin', 'dgit') } + if (workspaceTemplateName === 'semaphore') { + const isCircomActive = await plugin.call('manager', 'isActive', 'circuit-compiler') + + if (!isCircomActive) await plugin.call('manager', 'activatePlugin', 'circuit-compiler') + } // this call needs to be here after the callback because it calls dGitProvider which also calls this function and that would cause an infinite loop await plugin.setWorkspaces(await getWorkspaces()) }).catch((error) => { - dispatch(createWorkspaceError({ error })) + dispatch(createWorkspaceError(error.message)) cb && cb(error) }) return promise @@ -269,19 +274,19 @@ export const workspaceExists = async (name: string) => { export const fetchWorkspaceDirectory = async (path: string) => { if (!path) return const provider = plugin.fileManager.currentFileProvider() - const promise = new Promise((resolve) => { - provider.resolveDirectory(path, (error, fileTree) => { + const promise: Promise = new Promise((resolve) => { + provider.resolveDirectory(path, (error, fileTree: FileTree) => { if (error) console.error(error) resolve(fileTree) }) }) - dispatch(fetchWorkspaceDirectoryRequest(promise)) + dispatch(fetchWorkspaceDirectoryRequest()) promise.then((fileTree) => { dispatch(fetchWorkspaceDirectorySuccess(path, fileTree)) }).catch((error) => { - dispatch(fetchWorkspaceDirectoryError({ error })) + dispatch(fetchWorkspaceDirectoryError(error.message)) }) return promise } diff --git a/libs/remix-ui/workspace/src/lib/components/file-explorer.tsx b/libs/remix-ui/workspace/src/lib/components/file-explorer.tsx index 69e072ff40..8ee532f4c8 100644 --- a/libs/remix-ui/workspace/src/lib/components/file-explorer.tsx +++ b/libs/remix-ui/workspace/src/lib/components/file-explorer.tsx @@ -3,7 +3,7 @@ import {useIntl} from 'react-intl' import {TreeView} from '@remix-ui/tree-view' // eslint-disable-line import {FileExplorerMenu} from './file-explorer-menu' // eslint-disable-line import {FileExplorerContextMenu} from './file-explorer-context-menu' // eslint-disable-line -import {FileExplorerProps, FileType, WorkSpaceState} from '../types' +import {FileExplorerProps, FileType, WorkSpaceState, WorkspaceElement} from '../types' import '../css/file-explorer.css' import {checkSpecialChars, extractNameFromKey, extractParentFromKey, joinPath} from '@remix-ui/helper' @@ -151,7 +151,7 @@ export const FileExplorer = (props: FileExplorerProps) => { ) } - const handleClickFile = (path: string, type: 'folder' | 'file' | 'gist') => { + const handleClickFile = (path: string, type: WorkspaceElement) => { if (!state.ctrlKey) { props.dispatchHandleClickFile(path, type) } else { diff --git a/libs/remix-ui/workspace/src/lib/components/file-render.tsx b/libs/remix-ui/workspace/src/lib/components/file-render.tsx index a23ed76df3..ffb08b2a31 100644 --- a/libs/remix-ui/workspace/src/lib/components/file-render.tsx +++ b/libs/remix-ui/workspace/src/lib/components/file-render.tsx @@ -1,6 +1,6 @@ // eslint-disable-next-line no-use-before-define import React, {SyntheticEvent, useEffect, useState} from 'react' -import {FileType} from '../types' +import {FileType, WorkspaceElement} from '../types' // eslint-disable-next-line @typescript-eslint/no-unused-vars import {TreeView, TreeViewItem} from '@remix-ui/tree-view' import {getPathIcon} from '@remix-ui/helper' @@ -14,7 +14,7 @@ export interface RenderFileProps { file: FileType index: number focusEdit: {element: string; type: string; isNew: boolean; lastEdit: string} - focusElement: {key: string; type: 'file' | 'folder' | 'gist'}[] + focusElement: {key: string; type: WorkspaceElement}[] focusContext: {element: string; x: number; y: number; type: string} ctrlKey: boolean expandPath: string[] diff --git a/libs/remix-ui/workspace/src/lib/reducers/workspace.ts b/libs/remix-ui/workspace/src/lib/reducers/workspace.ts index 1058dfb8a4..adec39d281 100644 --- a/libs/remix-ui/workspace/src/lib/reducers/workspace.ts +++ b/libs/remix-ui/workspace/src/lib/reducers/workspace.ts @@ -1,12 +1,8 @@ import {extractNameFromKey} from '@remix-ui/helper' -import {action, FileType} from '../types' +import {action, Actions, FileType, WorkspaceElement} from '../types' import * as _ from 'lodash' import {fileDecoration} from '@remix-ui/file-decorators' import {ROOT_PATH} from '../utils/constants' -interface Action { - type: string - payload: any -} export interface BrowserState { browser: { currentWorkspace: string @@ -63,7 +59,7 @@ export interface BrowserState { readonly: boolean popup: string focusEdit: string - focusElement: {key: string; type: 'file' | 'folder' | 'gist'}[] + focusElement: {key: string; type: WorkspaceElement}[] initializingFS: boolean gitConfig: {username: string; email: string; token: string} } @@ -121,15 +117,10 @@ export const browserInitialState: BrowserState = { gitConfig: {username: '', email: '', token: ''} } -export const browserReducer = (state = browserInitialState, action: Action) => { +export const browserReducer = (state = browserInitialState, action: Actions) => { switch (action.type) { case 'SET_CURRENT_WORKSPACE': { - const payload = action.payload as { - name: string - isGitRepo: boolean - branches?: {remote: any; name: string}[] - currentBranch?: string - } + const payload = action.payload const workspaces = state.browser.workspaces.find( ({name}) => name === payload.name ) @@ -147,12 +138,7 @@ export const browserReducer = (state = browserInitialState, action: Action) => { } case 'SET_WORKSPACES': { - const payload = action.payload as { - name: string - isGitRepo: boolean - branches?: {remote: any; name: string}[] - currentBranch?: string - }[] + const payload = action.payload return { ...state, @@ -164,7 +150,7 @@ export const browserReducer = (state = browserInitialState, action: Action) => { } case 'SET_MODE': { - const payload = action.payload as 'browser' | 'localhost' + const payload = action.payload return { ...state, @@ -191,7 +177,7 @@ export const browserReducer = (state = browserInitialState, action: Action) => { } case 'FETCH_DIRECTORY_SUCCESS': { - const payload = action.payload as {path: string; fileTree} + const payload = action.payload return { ...state, @@ -255,7 +241,7 @@ export const browserReducer = (state = browserInitialState, action: Action) => { } case 'FETCH_WORKSPACE_DIRECTORY_SUCCESS': { - const payload = action.payload as {path: string; fileTree} + const payload = action.payload return { ...state, @@ -302,14 +288,7 @@ export const browserReducer = (state = browserInitialState, action: Action) => { } case 'DISPLAY_NOTIFICATION': { - const payload = action.payload as { - title: string - message: string - actionOk: () => void - actionCancel: () => void - labelOk: string - labelCancel: string - } + const payload = action.payload return { ...state, @@ -335,7 +314,7 @@ export const browserReducer = (state = browserInitialState, action: Action) => { } case 'FILE_ADDED_SUCCESS': { - const payload = action.payload as string + const payload = action.payload return { ...state, @@ -365,11 +344,7 @@ export const browserReducer = (state = browserInitialState, action: Action) => { } case 'FOLDER_ADDED_SUCCESS': { - const payload = action.payload as { - path: string - folderPath: string - fileTree - } + const payload = action.payload return { ...state, @@ -404,7 +379,7 @@ export const browserReducer = (state = browserInitialState, action: Action) => { } case 'FILE_REMOVED_SUCCESS': { - const payload = action.payload as string + const payload = action.payload return { ...state, @@ -446,11 +421,7 @@ export const browserReducer = (state = browserInitialState, action: Action) => { } case 'ADD_INPUT_FIELD': { - const payload = action.payload as { - path: string - fileTree - type: 'file' | 'folder' - } + const payload = action.payload return { ...state, @@ -473,7 +444,7 @@ export const browserReducer = (state = browserInitialState, action: Action) => { } case 'REMOVE_INPUT_FIELD': { - const payload = action.payload as {path: string; fileTree} + const payload = action.payload return { ...state, @@ -496,7 +467,7 @@ export const browserReducer = (state = browserInitialState, action: Action) => { } case 'SET_READ_ONLY_MODE': { - const payload = action.payload as boolean + const payload = action.payload return { ...state, @@ -505,11 +476,7 @@ export const browserReducer = (state = browserInitialState, action: Action) => { } case 'FILE_RENAMED_SUCCESS': { - const payload = action.payload as { - path: string - oldPath: string - fileTree - } + const payload = action.payload return { ...state, @@ -543,12 +510,7 @@ export const browserReducer = (state = browserInitialState, action: Action) => { } case 'CREATE_WORKSPACE_SUCCESS': { - const payload = action.payload as { - name: string - isGitRepo: boolean - branches?: {remote: any; name: string}[] - currentBranch?: string - } + const payload = action.payload const workspaces = state.browser.workspaces.find( ({name}) => name === payload.name ) @@ -581,7 +543,7 @@ export const browserReducer = (state = browserInitialState, action: Action) => { } case 'RENAME_WORKSPACE': { - const payload = action.payload as {oldName: string; workspaceName: string} + const payload = action.payload let renamedWorkspace const workspaces = state.browser.workspaces.filter( ({name, isGitRepo, branches, currentBranch}) => { @@ -611,7 +573,7 @@ export const browserReducer = (state = browserInitialState, action: Action) => { } case 'DELETE_WORKSPACE': { - const payload = action.payload as string + const payload = action.payload const workspaces = state.browser.workspaces.filter( ({name}) => name && name !== payload ) @@ -626,7 +588,7 @@ export const browserReducer = (state = browserInitialState, action: Action) => { } case 'DISPLAY_POPUP_MESSAGE': { - const payload = action.payload as string + const payload = action.payload return { ...state, @@ -642,10 +604,7 @@ export const browserReducer = (state = browserInitialState, action: Action) => { } case 'SET_FOCUS_ELEMENT': { - const payload = action.payload as { - key: string - type: 'file' | 'folder' | 'gist' - }[] + const payload = action.payload return { ...state, @@ -654,7 +613,7 @@ export const browserReducer = (state = browserInitialState, action: Action) => { } case 'REMOVE_FOCUS_ELEMENT': { - const payload: string = action.payload + const payload = action.payload return { ...state, @@ -665,7 +624,7 @@ export const browserReducer = (state = browserInitialState, action: Action) => { } case 'SET_CONTEXT_MENU_ITEM': { - const payload = action.payload as action + const payload = action.payload return { ...state, @@ -801,7 +760,7 @@ export const browserReducer = (state = browserInitialState, action: Action) => { } case 'SET_CURRENT_WORKSPACE_BRANCHES': { - const payload: {remote: any; name: string}[] = action.payload + const payload = action.payload return { ...state, @@ -817,7 +776,7 @@ export const browserReducer = (state = browserInitialState, action: Action) => { } case 'SET_CURRENT_WORKSPACE_CURRENT_BRANCH': { - const payload: string = action.payload + const payload = action.payload return { ...state, @@ -833,7 +792,7 @@ export const browserReducer = (state = browserInitialState, action: Action) => { } case 'SET_CURRENT_WORKSPACE_IS_GITREPO': { - const payload: boolean = action.payload + const payload = action.payload return { ...state, @@ -849,7 +808,7 @@ export const browserReducer = (state = browserInitialState, action: Action) => { } case 'SET_GIT_CONFIG': { - const payload: {username: string; token: string; email: string} = + const payload = action.payload return { ...state, @@ -1138,7 +1097,7 @@ const addContextMenuItem = ( const removeContextMenuItem = ( state: BrowserState, - plugin + plugin: {name: string} ): { registeredMenuItems: action[] removedMenuItems: action[] diff --git a/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx b/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx index e19799971d..0b7cda677a 100644 --- a/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx +++ b/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx @@ -1,7 +1,7 @@ -import React, {useState, useEffect, useRef, useContext, SyntheticEvent, ChangeEvent, KeyboardEvent, MouseEvent} from 'react' // eslint-disable-line +import React, {useState, useEffect, useRef, useContext, ChangeEvent} from 'react' // eslint-disable-line import {FormattedMessage, useIntl} from 'react-intl' import {Dropdown} from 'react-bootstrap' -import {CustomIconsToggle, CustomMenu, CustomToggle, CustomTooltip, extractNameFromKey, extractParentFromKey} from '@remix-ui/helper' +import {CustomIconsToggle, CustomMenu, CustomToggle, extractNameFromKey, extractParentFromKey} from '@remix-ui/helper' import {FileExplorer} from './components/file-explorer' // eslint-disable-line import {FileSystemContext} from './contexts' import './css/remix-ui-workspace.css' @@ -385,7 +385,6 @@ export function Workspace() { // @ts-ignore uupsRadioRef.current.checked = false } else displayOzCustomRef.current.style.display = 'none' - // @ts-ignore let displayName = TEMPLATE_NAMES[(workspaceCreateTemplateInput.current && workspaceCreateTemplateInput.current.value) || 'remixDefault'] displayName = global.plugin.getAvailableWorkspaceName(displayName) @@ -729,6 +728,11 @@ export function Workspace() { {intl.formatMessage({id: 'filePanel.multiSigWallet'})} + + +
diff --git a/libs/remix-ui/workspace/src/lib/types/index.ts b/libs/remix-ui/workspace/src/lib/types/index.ts index 5ded4df7d4..ad91681659 100644 --- a/libs/remix-ui/workspace/src/lib/types/index.ts +++ b/libs/remix-ui/workspace/src/lib/types/index.ts @@ -5,7 +5,7 @@ import { fileDecoration } from '@remix-ui/file-decorators' import { RemixAppManager } from 'libs/remix-ui/plugin-manager/src/types' import { ViewPlugin } from '@remixproject/engine-web' -export type action = { name: string, type?: Array<'folder' | 'gist' | 'file' | 'workspace'>, path?: string[], extension?: string[], pattern?: string[], id: string, multiselect: boolean, label: string, sticky?: boolean, group: number } +export type action = { name: string, type?: Array, path?: string[], extension?: string[], pattern?: string[], id: string, multiselect: boolean, label: string, sticky?: boolean, group: number } export interface JSONStandardInput { language: "Solidity"; settings?: any, @@ -17,7 +17,7 @@ export interface JSONStandardInput { }; } export type MenuItems = action[] -export type WorkspaceTemplate = 'gist-template' | 'code-template' | 'remixDefault' | 'blank' | 'ozerc20' | 'zeroxErc20' | 'ozerc721' +export type WorkspaceTemplate = 'gist-template' | 'code-template' | 'remixDefault' | 'blank' | 'ozerc20' | 'zeroxErc20' | 'ozerc721' | 'semaphore' export interface WorkspaceProps { plugin: FilePanelType } @@ -88,7 +88,7 @@ export interface FileExplorerProps { focusEdit: string, hideIconsMenu: React.Dispatch>, showIconsMenu: boolean, - focusElement: { key: string, type: 'file' | 'folder' | 'gist' }[], + focusElement: { key: string, type: WorkspaceElement }[], dispatchCreateNewFile: (path: string, rootDir: string) => Promise, // eslint-disable-next-line no-undef modal:(title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void) => void, @@ -105,8 +105,8 @@ export interface FileExplorerProps { dispatchRunScript: (path: string) => Promise, dispatchPublishToGist: (path?: string, type?: string) => Promise, dispatchEmitContextMenuEvent: (cmd: customAction) => Promise, - dispatchHandleClickFile: (path: string, type: 'file' | 'folder' | 'gist') => Promise, - dispatchSetFocusElement: (elements: { key: string, type: 'file' | 'folder' | 'gist' }[]) => Promise, + dispatchHandleClickFile: (path: string, type: WorkspaceElement) => Promise, + dispatchSetFocusElement: (elements: { key: string, type: WorkspaceElement }[]) => Promise, dispatchFetchDirectory:(path: string) => Promise, dispatchRemoveInputField:(path: string) => Promise, dispatchAddInputField:(path: string, type: 'file' | 'folder') => Promise, @@ -114,7 +114,7 @@ export interface FileExplorerProps { dispatchMoveFile: (src: string, dest: string) => Promise, dispatchMoveFolder: (src: string, dest: string) => Promise, handlePasteClick: (dest: string, destType: string) => void - handleCopyClick: (path: string, type: 'folder' | 'gist' | 'file' | 'workspace') => void + handleCopyClick: (path: string, type: WorkspaceElement) => void addMenuItems: (items: MenuItems) => void removeMenuItems: (items: MenuItems) => void handleContextMenu: (pageX: number, pageY: number, path: string, content: string, type: string) => void @@ -171,7 +171,7 @@ export interface WorkSpaceState { actions: { id: string name: string - type?: Array<'folder' | 'gist' | 'file' | 'workspace'> + type?: Array path?: string[] extension?: string[] pattern?: string[] @@ -201,5 +201,107 @@ export type FileFocusContextType = { export type CopyElementType = { key: string - type: 'folder' | 'gist' | 'file' | 'workspace' -} \ No newline at end of file + type: WorkspaceElement +} + +export type FileTree = { + [x: string]: { + isDirectory: boolean + } +} + +export interface ActionPayloadTypes { + SET_CURRENT_WORKSPACE: { + name: string + isGitRepo: boolean + branches?: {remote: string | undefined; name: string}[] + currentBranch?: string + }, + SET_WORKSPACES: { + name: string + isGitRepo: boolean + branches?: {remote: string | undefined; name: string}[] + currentBranch?: string + }[], + SET_MODE: 'browser' | 'localhost', + FETCH_DIRECTORY_REQUEST: undefined | null, + FETCH_DIRECTORY_SUCCESS: { path: string; fileTree: FileTree }, + FETCH_DIRECTORY_ERROR: string, + FETCH_WORKSPACE_DIRECTORY_REQUEST: undefined | null, + FETCH_WORKSPACE_DIRECTORY_SUCCESS: { path: string; fileTree: FileTree }, + FETCH_WORKSPACE_DIRECTORY_ERROR: string, + DISPLAY_NOTIFICATION: { + title: string + message: string + actionOk: () => void + actionCancel: () => void + labelOk: string + labelCancel: string + }, + HIDE_NOTIFICATION: undefined | null, + FILE_ADDED_SUCCESS: string, + FOLDER_ADDED_SUCCESS: { + path: string + folderPath: string + fileTree: FileTree + }, + FILE_REMOVED_SUCCESS: string, + ROOT_FOLDER_CHANGED: string, + ADD_INPUT_FIELD: { + path: string + fileTree: FileTree + type: 'file' | 'folder' + }, + REMOVE_INPUT_FIELD: { path: string; }, + SET_READ_ONLY_MODE: boolean, + FILE_RENAMED_SUCCESS: { + path: string + oldPath: string + fileTree: FileTree + }, + CREATE_WORKSPACE_REQUEST: undefined | null, + CREATE_WORKSPACE_SUCCESS: { + name: string + isGitRepo: boolean + branches?: { remote: string | undefined; name: string }[] + currentBranch?: string + }, + CREATE_WORKSPACE_ERROR: string, + RENAME_WORKSPACE: { oldName: string; workspaceName: string }, + DELETE_WORKSPACE: string, + DISPLAY_POPUP_MESSAGE: string, + HIDE_POPUP_MESSAGE: undefined | null, + SET_FOCUS_ELEMENT: { + key: string + type: WorkspaceElement + }[], + REMOVE_FOCUS_ELEMENT: string, + SET_CONTEXT_MENU_ITEM: action, + REMOVE_CONTEXT_MENU_ITEM: { name: string }, + SET_EXPAND_PATH: string[], + LOAD_LOCALHOST_REQUEST: undefined | null, + LOAD_LOCALHOST_SUCCESS: undefined | null, + LOAD_LOCALHOST_ERROR: string, + CLONE_REPOSITORY_REQUEST: undefined | null, + CLONE_REPOSITORY_SUCCESS: undefined | null, + CLONE_REPOSITORY_FAILED: undefined | null, + FS_INITIALIZATION_COMPLETED: undefined | null, + SET_FILE_DECORATION_SUCCESS: fileDecoration[], + SET_CURRENT_WORKSPACE_BRANCHES: { remote: string | undefined; name: string }[], + SET_CURRENT_WORKSPACE_CURRENT_BRANCH: string, + SET_CURRENT_WORKSPACE_IS_GITREPO: boolean, + SET_GIT_CONFIG: { + username: string; + token: string; + email: string + } +} + +export interface Action { + type: T, + payload: ActionPayloadTypes[T] +} + +export type Actions = {[A in keyof ActionPayloadTypes]: Action}[keyof ActionPayloadTypes] + +export type WorkspaceElement = 'folder' | 'gist' | 'file' | 'workspace' diff --git a/libs/remix-ui/workspace/src/lib/utils/constants.ts b/libs/remix-ui/workspace/src/lib/utils/constants.ts index 688c4731d6..0dc54144bc 100644 --- a/libs/remix-ui/workspace/src/lib/utils/constants.ts +++ b/libs/remix-ui/workspace/src/lib/utils/constants.ts @@ -80,5 +80,6 @@ export const TEMPLATE_NAMES = { 'ozerc721': 'OpenZeppelin ERC721', 'ozerc1155': 'OpenZeppelin ERC1155', 'zeroxErc20': '0xProject ERC20', - 'gnosisSafeMultisig': 'Gnosis Safe' + 'gnosisSafeMultisig': 'Gnosis Safe', + 'semaphore': 'Semaphore' } diff --git a/libs/remix-ws-templates/src/index.ts b/libs/remix-ws-templates/src/index.ts index e11a422750..b99e70ea62 100644 --- a/libs/remix-ws-templates/src/index.ts +++ b/libs/remix-ws-templates/src/index.ts @@ -5,6 +5,7 @@ export { default as ozerc721 } from './templates/ozerc721' export { default as ozerc1155 } from './templates/ozerc1155' export { default as zeroxErc20 } from './templates/zeroxErc20' export { default as gnosisSafeMultisig } from './templates/gnosisSafeMultisig' +export { default as semaphore } from './templates/semaphore' export { contractDeployerScripts } from './script-templates/contract-deployer' export { etherscanScripts } from './script-templates/etherscan' diff --git a/libs/remix-ws-templates/src/templates/semaphore/README.txt b/libs/remix-ws-templates/src/templates/semaphore/README.txt new file mode 100644 index 0000000000..19034d3b23 --- /dev/null +++ b/libs/remix-ws-templates/src/templates/semaphore/README.txt @@ -0,0 +1,9 @@ +REMIX CIRCOM WORKSPACE + +Welcome to the Remix Circom Workspace. This workspace becomes available when you create a new workspace using the 'Circom' template. +Directory Structure + +The workspace comprises two main directories: + + circuits: Contains sample semaphore contracts. These can be compiled to generate a witness. + scripts: Provides a sample script designed for a trusted setup using snarkjs. This script also aids in generating Solidity code, which is essential for on-chain deployment. \ No newline at end of file diff --git a/libs/remix-ws-templates/src/templates/semaphore/circuits/semaphore.circom b/libs/remix-ws-templates/src/templates/semaphore/circuits/semaphore.circom new file mode 100644 index 0000000000..0e340a952d --- /dev/null +++ b/libs/remix-ws-templates/src/templates/semaphore/circuits/semaphore.circom @@ -0,0 +1,90 @@ +pragma circom 2.0.0; + +include "circomlib/poseidon.circom"; +include "./tree.circom"; + +template CalculateSecret() { + signal input identityNullifier; + signal input identityTrapdoor; + signal output out; + + component poseidon = Poseidon(2); + + poseidon.inputs[0] <== identityNullifier; + poseidon.inputs[1] <== identityTrapdoor; + + out <== poseidon.out; +} + +template CalculateIdentityCommitment() { + signal input secret; + + signal output out; + + component poseidon = Poseidon(1); + + poseidon.inputs[0] <== secret; + + out <== poseidon.out; +} + +template CalculateNullifierHash() { + signal input externalNullifier; + signal input identityNullifier; + + signal output out; + + component poseidon = Poseidon(2); + + poseidon.inputs[0] <== externalNullifier; + poseidon.inputs[1] <== identityNullifier; + + out <== poseidon.out; +} + +// credits to : https://github.com/semaphore-protocol/semaphore +// The current Semaphore smart contracts require nLevels <= 32 and nLevels >= 16. +template Semaphore(nLevels) { + signal input identityNullifier; + signal input identityTrapdoor; + signal input treePathIndices[nLevels]; + signal input treeSiblings[nLevels]; + + signal input signalHash; + signal input externalNullifier; + + signal output root; + signal output nullifierHash; + + component calculateSecret = CalculateSecret(); + calculateSecret.identityNullifier <== identityNullifier; + calculateSecret.identityTrapdoor <== identityTrapdoor; + + signal secret; + secret <== calculateSecret.out; + + component calculateIdentityCommitment = CalculateIdentityCommitment(); + calculateIdentityCommitment.secret <== secret; + + component calculateNullifierHash = CalculateNullifierHash(); + calculateNullifierHash.externalNullifier <== externalNullifier; + calculateNullifierHash.identityNullifier <== identityNullifier; + + component inclusionProof = MerkleTreeInclusionProof(nLevels); + inclusionProof.leaf <== calculateIdentityCommitment.out; + + for (var i = 0; i < nLevels; i++) { + inclusionProof.siblings[i] <== treeSiblings[i]; + inclusionProof.pathIndices[i] <== treePathIndices[i]; + } + + root <== inclusionProof.root; + + // Dummy square to prevent tampering signalHash. + signal signalHashSquared; + signalHashSquared <== signalHash * signalHash; + + nullifierHash <== calculateNullifierHash.out; +} + +component main {public [signalHash, externalNullifier]} = Semaphore(20); \ No newline at end of file diff --git a/libs/remix-ws-templates/src/templates/semaphore/circuits/simple.circom b/libs/remix-ws-templates/src/templates/semaphore/circuits/simple.circom new file mode 100644 index 0000000000..9a2120df7a --- /dev/null +++ b/libs/remix-ws-templates/src/templates/semaphore/circuits/simple.circom @@ -0,0 +1,11 @@ +pragma circom 2.0.0; + +template Multiplier2() { + signal input a; + signal input b; + signal output c; + c <== a*b; + } + + component main = Multiplier2(); + \ No newline at end of file diff --git a/libs/remix-ws-templates/src/templates/semaphore/circuits/tree.circom b/libs/remix-ws-templates/src/templates/semaphore/circuits/tree.circom new file mode 100644 index 0000000000..764d388968 --- /dev/null +++ b/libs/remix-ws-templates/src/templates/semaphore/circuits/tree.circom @@ -0,0 +1,40 @@ +pragma circom 2.0.0; + +include "circomlib/poseidon.circom"; +include "circomlib/mux1.circom"; + +template MerkleTreeInclusionProof(nLevels) { + signal input leaf; + signal input pathIndices[nLevels]; + signal input siblings[nLevels]; + + signal output root; + + component poseidons[nLevels]; + component mux[nLevels]; + + signal hashes[nLevels + 1]; + hashes[0] <== leaf; + + for (var i = 0; i < nLevels; i++) { + pathIndices[i] * (1 - pathIndices[i]) === 0; + + poseidons[i] = Poseidon(2); + mux[i] = MultiMux1(2); + + mux[i].c[0][0] <== hashes[i]; + mux[i].c[0][1] <== siblings[i]; + + mux[i].c[1][0] <== siblings[i]; + mux[i].c[1][1] <== hashes[i]; + + mux[i].s <== pathIndices[i]; + + poseidons[i].inputs[0] <== mux[i].out[0]; + poseidons[i].inputs[1] <== mux[i].out[1]; + + hashes[i + 1] <== poseidons[i].out; + } + + root <== hashes[nLevels]; +} \ No newline at end of file diff --git a/libs/remix-ws-templates/src/templates/semaphore/index.ts b/libs/remix-ws-templates/src/templates/semaphore/index.ts new file mode 100644 index 0000000000..8e12a1ee95 --- /dev/null +++ b/libs/remix-ws-templates/src/templates/semaphore/index.ts @@ -0,0 +1,18 @@ +export default async () => { + return { + // @ts-ignore + 'circuits/semaphore.circom': (await import('raw-loader!./circuits/semaphore.circom')).default, + // @ts-ignore + 'circuits/simple.circom': (await import('!!raw-loader!./circuits/simple.circom')).default, + // @ts-ignore + 'circuits/tree.circom': (await import('!!raw-loader!./circuits/tree.circom')).default, + // @ts-ignore + 'scripts/run_setup.ts': (await import('!!raw-loader!./scripts/run_setup.ts')).default, + // @ts-ignore + 'scripts/run_verification.ts': (await import('!!raw-loader!./scripts/run_verification.ts')).default, + // @ts-ignore + 'templates/groth16_verifier.sol.ejs': (await import('!!raw-loader!./templates/groth16_verifier.sol.ejs')).default, + // @ts-ignore + 'README.txt': (await import('raw-loader!./README.txt')).default + } +} \ No newline at end of file diff --git a/libs/remix-ws-templates/src/templates/semaphore/scripts/run_setup.ts b/libs/remix-ws-templates/src/templates/semaphore/scripts/run_setup.ts new file mode 100644 index 0000000000..1d8505b71d --- /dev/null +++ b/libs/remix-ws-templates/src/templates/semaphore/scripts/run_setup.ts @@ -0,0 +1,72 @@ +import { ethers, BigNumber } from 'ethers' + +// eslint-disable-next-line @typescript-eslint/no-var-requires +const snarkjs = require('snarkjs'); + +const logger = { + info: (...args) => console.log(...args), + debug: (...args) => console.log(...args) +}; + +/** + * Creates a keccak256 hash of a message compatible with the SNARK scalar modulus. + * @param message The message to be hashed. + * @returns The message digest. + */ +function hash(message: any): bigint { + message = BigNumber.from(message).toTwos(256).toHexString() + message = ethers.utils.zeroPad(message, 32) + return BigInt(ethers.utils.keccak256(message)) >> BigInt(8) +} + +(async () => { + try { + // @ts-ignore + await remix.call('circuit-compiler', 'generateR1cs', 'circuits/semaphore.circom'); + + const ptau_final = "https://ipfs-cluster.ethdevops.io/ipfs/QmTiT4eiYz5KF7gQrDsgfCSTRv3wBPYJ4bRN1MmTRshpnW"; + // @ts-ignore + const r1csBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/semaphore.r1cs', true); + // @ts-ignore + const r1cs = new Uint8Array(r1csBuffer); + const zkey_0 = { type: "mem" }; + const zkey_1 = { type: "mem" }; + const zkey_final = { type: "mem" }; + + console.log('newZkey') + await snarkjs.zKey.newZKey(r1cs, ptau_final, zkey_0); + + console.log('contribute') + await snarkjs.zKey.contribute(zkey_0, zkey_1, "p2_C1", "pa_Entropy1"); + + console.log('beacon') + await snarkjs.zKey.beacon(zkey_1, zkey_final, "B3", "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20", 10); + + console.log('verifyFromR1cs') + const verifyFromR1csResult = await snarkjs.zKey.verifyFromR1cs(r1cs, ptau_final, zkey_final); + console.assert(verifyFromR1csResult); + + console.log('verifyFromInit') + const verifyFromInit = await snarkjs.zKey.verifyFromInit(zkey_0, ptau_final, zkey_final); + console.assert(verifyFromInit); + + console.log('exportVerificationKey') + const vKey = await snarkjs.zKey.exportVerificationKey(zkey_final) + await remix.call('fileManager', 'writeFile', './zk/build/verification_key.json', JSON.stringify(vKey)) + + const templates = { + groth16: await remix.call('fileManager', 'readFile', 'templates/groth16_verifier.sol.ejs') + } + const solidityContract = await snarkjs.zKey.exportSolidityVerifier(zkey_final, templates) + + await remix.call('fileManager', 'writeFile', './zk/build/zk_verifier.sol', solidityContract) + + console.log('buffer', (zkey_final as any).data.length) + await remix.call('fileManager', 'writeFile', './zk/build/zk_setup.txt', JSON.stringify(Array.from(((zkey_final as any).data)))) + + console.log('setup done.') + + } catch (e) { + console.error(e.message) + } +})() \ No newline at end of file diff --git a/libs/remix-ws-templates/src/templates/semaphore/scripts/run_verification.ts b/libs/remix-ws-templates/src/templates/semaphore/scripts/run_verification.ts new file mode 100644 index 0000000000..e97e4074e9 --- /dev/null +++ b/libs/remix-ws-templates/src/templates/semaphore/scripts/run_verification.ts @@ -0,0 +1,102 @@ +import { ethers, BigNumber } from 'ethers' +import { IncrementalMerkleTree } from "@zk-kit/incremental-merkle-tree" +import { poseidon } from "circomlibjs" // v0.0.8 + +// eslint-disable-next-line @typescript-eslint/no-var-requires +const snarkjs = require('snarkjs'); + +const logger = { + info: (...args) => console.log(...args), + debug: (...args) => console.log(...args), + error: (...args) => console.error(...args), +} + +/** + * Creates a keccak256 hash of a message compatible with the SNARK scalar modulus. + * @param message The message to be hashed. + * @returns The message digest. + */ +function hash(message: any): bigint { + message = BigNumber.from(message).toTwos(256).toHexString() + message = ethers.utils.zeroPad(message, 32) + return BigInt(ethers.utils.keccak256(message)) >> BigInt(8) +} + +(async () => { + try { + // @ts-ignore + const r1csBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/semaphore.r1cs', true); + // @ts-ignore + const r1cs = new Uint8Array(r1csBuffer); + // @ts-ignore + const wasmBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/semaphore.wasm', true); + // @ts-ignore + const wasm = new Uint8Array(wasmBuffer); + + const zkey_final = { + type: "mem", + data: new Uint8Array(JSON.parse(await remix.call('fileManager', 'readFile', './zk/build/zk_setup.txt'))) + } + const wtns = { type: "mem" }; + + const vKey = JSON.parse(await remix.call('fileManager', 'readFile', './zk/build/verification_key.json')) + + // build list of identity commitments + const secrets = [] + const identityCommitments = [] + for (let k = 0; k < 2; k++) { + const identityTrapdoor = BigInt(ethers.utils.hexlify(ethers.utils.randomBytes(32))) + const identityNullifier = BigInt(ethers.utils.hexlify(ethers.utils.randomBytes(32))) + secrets.push({identityTrapdoor, identityNullifier}) + + const secret = poseidon([identityNullifier, identityTrapdoor]) + const identityCommitment = poseidon([secret]) + identityCommitments.push(identityCommitment) + } + //console.log('incremental tree', identityCommitments.map((x) => x.toString())) + + let tree + + try { + tree = new IncrementalMerkleTree(poseidon, 20, BigInt(0), 2, identityCommitments) // Binary tree. + } catch (e) { + console.error(e.message) + return + } + const index = tree.indexOf(identityCommitments[0]) + + console.log(index.toString()) + + const proof1 = tree.createProof(0) + + console.log('prepare signals for id ', identityCommitments[0].toString(), tree.indexOf(identityCommitments[0]), proof1.siblings.map((x)=> x.toString())) + + const signals = { + identityTrapdoor: secrets[0].identityTrapdoor, + identityNullifier: secrets[0].identityNullifier, + treePathIndices: proof1.pathIndices, + treeSiblings: proof1.siblings, + externalNullifier: hash(42), + signalHash: hash(ethers.utils.formatBytes32String("Hello World")) + } + + console.log('calculate') + await snarkjs.wtns.calculate(signals, wasm, wtns); + + console.log('check') + await snarkjs.wtns.check(r1cs, wtns, logger); + + + console.log('prove') + const { proof, publicSignals } = await snarkjs.groth16.prove(zkey_final, wtns); + + const verified = await snarkjs.groth16.verify(vKey, publicSignals, proof, logger); + console.log('zk proof validity', verified); + proof1.root.toString() === publicSignals[0] ? console.log('merkle proof valid') : console.log('merkle proof invalid') + + + + } catch (e) { + console.error(e.message) + } +})() \ No newline at end of file diff --git a/libs/remix-ws-templates/src/templates/semaphore/templates/groth16_verifier.sol.ejs b/libs/remix-ws-templates/src/templates/semaphore/templates/groth16_verifier.sol.ejs new file mode 100644 index 0000000000..692aedf612 --- /dev/null +++ b/libs/remix-ws-templates/src/templates/semaphore/templates/groth16_verifier.sol.ejs @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: GPL-3.0 +/* + Copyright 2021 0KIMS association. + + This file is generated with [snarkJS](https://github.com/iden3/snarkjs). + + snarkJS is a free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + snarkJS is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + License for more details. + + You should have received a copy of the GNU General Public License + along with snarkJS. If not, see . +*/ + +pragma solidity >=0.7.0 <0.9.0; + +contract Groth16Verifier { + // Scalar field size + uint256 constant r = 21888242871839275222246405745257275088548364400416034343698204186575808495617; + // Base field size + uint256 constant q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; + + // Verification Key data + uint256 constant alphax = <%= vk_alpha_1[0] %>; + uint256 constant alphay = <%= vk_alpha_1[1] %>; + uint256 constant betax1 = <%= vk_beta_2[0][1] %>; + uint256 constant betax2 = <%= vk_beta_2[0][0] %>; + uint256 constant betay1 = <%= vk_beta_2[1][1] %>; + uint256 constant betay2 = <%= vk_beta_2[1][0] %>; + uint256 constant gammax1 = <%= vk_gamma_2[0][1] %>; + uint256 constant gammax2 = <%= vk_gamma_2[0][0] %>; + uint256 constant gammay1 = <%= vk_gamma_2[1][1] %>; + uint256 constant gammay2 = <%= vk_gamma_2[1][0] %>; + uint256 constant deltax1 = <%= vk_delta_2[0][1] %>; + uint256 constant deltax2 = <%= vk_delta_2[0][0] %>; + uint256 constant deltay1 = <%= vk_delta_2[1][1] %>; + uint256 constant deltay2 = <%= vk_delta_2[1][0] %>; + + <% for (let i=0; i + uint256 constant IC<%=i%>x = <%=IC[i][0]%>; + uint256 constant IC<%=i%>y = <%=IC[i][1]%>; + <% } %> + + // Memory data + uint16 constant pVk = 0; + uint16 constant pPairing = 128; + + uint16 constant pLastMem = 896; + + function verifyProof(uint[2] calldata _pA, uint[2][2] calldata _pB, uint[2] calldata _pC, uint[<%=IC.length-1%>] calldata _pubSignals) public view returns (bool) { + assembly { + function checkField(v) { + if iszero(lt(v, q)) { + mstore(0, 0) + return(0, 0x20) + } + } + + // G1 function to multiply a G1 value(x,y) to value in an address + function g1_mulAccC(pR, x, y, s) { + let success + let mIn := mload(0x40) + mstore(mIn, x) + mstore(add(mIn, 32), y) + mstore(add(mIn, 64), s) + + success := staticcall(sub(gas(), 2000), 7, mIn, 96, mIn, 64) + + if iszero(success) { + mstore(0, 0) + return(0, 0x20) + } + + mstore(add(mIn, 64), mload(pR)) + mstore(add(mIn, 96), mload(add(pR, 32))) + + success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64) + + if iszero(success) { + mstore(0, 0) + return(0, 0x20) + } + } + + function checkPairing(pA, pB, pC, pubSignals, pMem) -> isOk { + let _pPairing := add(pMem, pPairing) + let _pVk := add(pMem, pVk) + + mstore(_pVk, IC0x) + mstore(add(_pVk, 32), IC0y) + + // Compute the linear combination vk_x + <% for (let i = 1; i <= nPublic; i++) { %> + g1_mulAccC(_pVk, IC<%=i%>x, IC<%=i%>y, calldataload(add(pubSignals, <%=(i-1)*32%>))) + <% } %> + + // -A + mstore(_pPairing, calldataload(pA)) + mstore(add(_pPairing, 32), mod(sub(q, calldataload(add(pA, 32))), q)) + + // B + mstore(add(_pPairing, 64), calldataload(pB)) + mstore(add(_pPairing, 96), calldataload(add(pB, 32))) + mstore(add(_pPairing, 128), calldataload(add(pB, 64))) + mstore(add(_pPairing, 160), calldataload(add(pB, 96))) + + // alpha1 + mstore(add(_pPairing, 192), alphax) + mstore(add(_pPairing, 224), alphay) + + // beta2 + mstore(add(_pPairing, 256), betax1) + mstore(add(_pPairing, 288), betax2) + mstore(add(_pPairing, 320), betay1) + mstore(add(_pPairing, 352), betay2) + + // vk_x + mstore(add(_pPairing, 384), mload(add(pMem, pVk))) + mstore(add(_pPairing, 416), mload(add(pMem, add(pVk, 32)))) + + + // gamma2 + mstore(add(_pPairing, 448), gammax1) + mstore(add(_pPairing, 480), gammax2) + mstore(add(_pPairing, 512), gammay1) + mstore(add(_pPairing, 544), gammay2) + + // C + mstore(add(_pPairing, 576), calldataload(pC)) + mstore(add(_pPairing, 608), calldataload(add(pC, 32))) + + // delta2 + mstore(add(_pPairing, 640), deltax1) + mstore(add(_pPairing, 672), deltax2) + mstore(add(_pPairing, 704), deltay1) + mstore(add(_pPairing, 736), deltay2) + + + let success := staticcall(sub(gas(), 2000), 8, _pPairing, 768, _pPairing, 0x20) + + isOk := and(success, mload(_pPairing)) + } + + let pMem := mload(0x40) + mstore(0x40, add(pMem, pLastMem)) + + // Validate that all evaluations ∈ F + <% for (let i=0; i + checkField(calldataload(add(_pubSignals, <%=i*32%>))) + <% } %> + + // Validate all evaluations + let isValid := checkPairing(_pA, _pB, _pC, _pubSignals, pMem) + + mstore(0, isValid) + return(0, 0x20) + } + } + } \ No newline at end of file