Merge pull request #4512 from ethereum/fix_circom_ref

Fix circom path & add sindri
pull/5370/head
yann300 9 months ago committed by GitHub
commit 50eba204e3
  1. 3
      apps/remix-ide/src/app/editor/editor.js
  2. 10
      apps/remix-ide/src/app/files/fileManager.ts
  3. 2
      apps/remix-ide/src/app/tabs/locales/en/filePanel.json
  4. 3
      apps/remix-ide/src/app/tabs/locales/en/settings.json
  5. 3
      libs/remix-ui/editor/src/lib/remix-ui-editor.tsx
  6. 5
      libs/remix-ui/settings/src/lib/constants.ts
  7. 10
      libs/remix-ui/settings/src/lib/remix-ui-settings.tsx
  8. 77
      libs/remix-ui/settings/src/lib/sindri-settings.tsx
  9. 13
      libs/remix-ui/settings/src/types/index.ts
  10. 12
      libs/remix-ui/workspace/src/lib/actions/workspace.ts
  11. 14
      libs/remix-ui/workspace/src/lib/components/workspace-hamburger.tsx
  12. 1
      libs/remix-ws-templates/src/index.ts
  13. 4
      libs/remix-ws-templates/src/script-templates/sindri/.sindriignore
  14. 120
      libs/remix-ws-templates/src/script-templates/sindri/README.md
  15. 94
      libs/remix-ws-templates/src/script-templates/sindri/index.ts
  16. 7
      libs/remix-ws-templates/src/script-templates/sindri/run_compile.ts
  17. 15
      libs/remix-ws-templates/src/script-templates/sindri/run_prove.ts
  18. 9
      libs/remix-ws-templates/src/script-templates/sindri/sindri.json
  19. 139
      libs/remix-ws-templates/src/script-templates/sindri/utils.ts
  20. 8
      projects.json

@ -52,7 +52,8 @@ class Editor extends Plugin {
cairo: 'cairo', cairo: 'cairo',
ts: 'typescript', ts: 'typescript',
move: 'move', move: 'move',
circom: 'circom' circom: 'circom',
nr: 'rust'
} }
this.activated = false this.activated = false

@ -24,7 +24,7 @@ const profile = {
methods: ['closeAllFiles', 'closeFile', 'file', 'exists', 'open', 'writeFile', 'writeMultipleFiles', 'writeFileNoRewrite', methods: ['closeAllFiles', 'closeFile', 'file', 'exists', 'open', 'writeFile', 'writeMultipleFiles', 'writeFileNoRewrite',
'readFile', 'copyFile', 'copyDir', 'rename', 'mkdir', 'readdir', 'dirList', 'fileList', 'remove', 'getCurrentFile', 'getFile', 'readFile', 'copyFile', 'copyDir', 'rename', 'mkdir', 'readdir', 'dirList', 'fileList', 'remove', 'getCurrentFile', 'getFile',
'getFolder', 'setFile', 'switchFile', 'refresh', 'getProviderOf', 'getProviderByName', 'getPathFromUrl', 'getUrlFromPath', 'getFolder', 'setFile', 'switchFile', 'refresh', 'getProviderOf', 'getProviderByName', 'getPathFromUrl', 'getUrlFromPath',
'saveCurrentFile', 'setBatchFiles', 'isGitRepo', 'isFile', 'isDirectory', 'hasGitSubmodule' 'saveCurrentFile', 'setBatchFiles', 'isGitRepo', 'isFile', 'isDirectory', 'hasGitSubmodule', 'copyFolderToJson'
], ],
kind: 'file-system' kind: 'file-system'
} }
@ -1041,6 +1041,14 @@ class FileManager extends Plugin {
throw new Error(e) throw new Error(e)
} }
} }
async copyFolderToJson(folder: string) {
const provider = this.currentFileProvider()
if (provider && provider.copyFolderToJson) {
return await provider.copyFolderToJson(folder)
}
throw new Error('copyFolderToJson not available')
}
} }
module.exports = FileManager module.exports = FileManager

@ -34,6 +34,8 @@
"filePanel.tssoltestghaction": "Mocha Chai Test Workflow", "filePanel.tssoltestghaction": "Mocha Chai Test Workflow",
"filePanel.workspace.addscriptetherscan": "Adds scripts which can be used to interact with the Etherscan API", "filePanel.workspace.addscriptetherscan": "Adds scripts which can be used to interact with the Etherscan API",
"filePanel.addscriptetherscan": "Add Etherscan scripts", "filePanel.addscriptetherscan": "Add Etherscan scripts",
"filePanel.workspace.addscriptsindri": "Adds scripts for interacting with Sindri, a zk proof generation remote service",
"filePanel.addscriptsindri": "Add Sindri ZK scripts",
"filePanel.workspace.addscriptdeployer": "Adds scripts which can be used to deploy contracts", "filePanel.workspace.addscriptdeployer": "Adds scripts which can be used to deploy contracts",
"filePanel.addscriptdeployer": "Add contract deployer scripts", "filePanel.addscriptdeployer": "Add contract deployer scripts",
"filePanel.workspace.slitherghaction": "Adds a preset yml file to run slither analysis on github actions CI", "filePanel.workspace.slitherghaction": "Adds a preset yml file to run slither analysis on github actions CI",

@ -17,6 +17,9 @@
"settings.etherscanTokenTitle": "EtherScan Access Token", "settings.etherscanTokenTitle": "EtherScan Access Token",
"settings.etherscanAccessTokenText": "Manage the api key used to interact with Etherscan.", "settings.etherscanAccessTokenText": "Manage the api key used to interact with Etherscan.",
"settings.etherscanAccessTokenText2": "Go to Etherscan api key page (link below) to create a new api key and save it in Remix.", "settings.etherscanAccessTokenText2": "Go to Etherscan api key page (link below) to create a new api key and save it in Remix.",
"settings.sindriAccessTokenTitle": "Sindri Credentials",
"settings.sindriAccessTokenText": "The access token is used to compile ZKP circuits and generate proofs with Sindri.",
"settings.sindriAccessTokenText2":"Go to the Sindri account creation page (link below) to create a new token and save it in Remix.",
"settings.save": "Save", "settings.save": "Save",
"settings.remove": "Remove", "settings.remove": "Remove",
"settings.themes": "Themes", "settings.themes": "Themes",

@ -858,6 +858,9 @@ export const EditorUI = (props: EditorUIProps) => {
monacoRef.current.languages.register({ id: 'remix-move' }) monacoRef.current.languages.register({ id: 'remix-move' })
monacoRef.current.languages.register({ id: 'remix-circom' }) monacoRef.current.languages.register({ id: 'remix-circom' })
// Allow JSON schema requests
monacoRef.current.languages.json.jsonDefaults.setDiagnosticsOptions({enableSchemaRequest: true})
// Register a tokens provider for the language // Register a tokens provider for the language
monacoRef.current.languages.setMonarchTokensProvider('remix-solidity', solidityTokensProvider as any) monacoRef.current.languages.setMonarchTokensProvider('remix-solidity', solidityTokensProvider as any)
monacoRef.current.languages.setLanguageConfiguration('remix-solidity', solidityLanguageConfig as any) monacoRef.current.languages.setLanguageConfiguration('remix-solidity', solidityLanguageConfig as any)

@ -3,6 +3,7 @@ export const textDark = 'text-dark'
export const gitAccessTokenLink = 'https://github.com/settings/tokens/new?scopes=gist,repo&description=Remix%20IDE%20Token' export const gitAccessTokenLink = 'https://github.com/settings/tokens/new?scopes=gist,repo&description=Remix%20IDE%20Token'
export const etherscanTokenLink = 'https://etherscan.io/myapikey' export const etherscanTokenLink = 'https://etherscan.io/myapikey'
export const sindriAccessTokenLink = 'https://sindri.app'
export const labels = { export const labels = {
'gist': { 'gist': {
'link': gitAccessTokenLink, 'link': gitAccessTokenLink,
@ -11,5 +12,9 @@ export const labels = {
'etherscan': { 'etherscan': {
'link': etherscanTokenLink, 'link': etherscanTokenLink,
'key': 'etherscan-access-token' 'key': 'etherscan-access-token'
},
'sindri': {
'link': sindriAccessTokenLink,
'key': 'sindri-access-token'
} }
} }

@ -28,6 +28,7 @@ import {RemixUiLocaleModule, LocaleModule} from '@remix-ui/locale-module'
import {FormattedMessage, useIntl} from 'react-intl' import {FormattedMessage, useIntl} from 'react-intl'
import {GithubSettings} from './github-settings' import {GithubSettings} from './github-settings'
import {EtherscanSettings} from './etherscan-settings' import {EtherscanSettings} from './etherscan-settings'
import {SindriSettings} from './sindri-settings'
/* eslint-disable-next-line */ /* eslint-disable-next-line */
export interface RemixUiSettingsProps { export interface RemixUiSettingsProps {
@ -599,6 +600,15 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
}} }}
config={props.config} config={props.config}
/> />
<SindriSettings
saveToken={(sindriToken: string) => {
saveTokenToast(props.config, dispatchToast, sindriToken, 'sindri-access-token')
}}
removeToken={() => {
removeTokenToast(props.config, dispatchToast, 'sindri-access-token')
}}
config={props.config}
/>
{swarmSettings()} {swarmSettings()}
{ipfsSettings()} {ipfsSettings()}
<RemixUiThemeModule themeModule={props._deps.themeModule} /> <RemixUiThemeModule themeModule={props._deps.themeModule} />

@ -0,0 +1,77 @@
import {CopyToClipboard} from '@remix-ui/clipboard'
import {CustomTooltip} from '@remix-ui/helper'
import React, {useEffect, useState} from 'react'
import {FormattedMessage, useIntl} from 'react-intl'
import {SindriSettingsProps} from '../types'
import {sindriAccessTokenLink} from './constants'
export function SindriSettings(props: SindriSettingsProps) {
const [sindriToken, setSindriToken] = useState<string>('')
const intl = useIntl()
useEffect(() => {
if (props.config) {
const sindriToken = props.config.get('settings/sindri-access-token') || ''
setSindriToken(sindriToken)
}
}, [props.config])
const handleChangeTokenState = (event) => {
const token = event.target.value ? event.target.value.trim() : event.target.value
setSindriToken(token)
}
// api key settings
const saveSindriToken = () => {
props.saveToken(sindriToken)
}
const removeToken = () => {
setSindriToken('')
props.removeToken()
}
return (
<div className="border-top">
<div className="card-body pt-3 pb-2">
<h6 className="card-title">
<FormattedMessage id="settings.sindriAccessTokenTitle" />
</h6>
<p className="mb-1">
<FormattedMessage id="settings.sindriAccessTokenText" />
</p>
<p className="">
<FormattedMessage id="settings.sindriAccessTokenText2" />
</p>
<p className="mb-1">
<a className="text-primary" target="_blank" href={sindriAccessTokenLink}>
{sindriAccessTokenLink}
</a>
</p>
<div>
<label className="mb-0 pb-0">
<FormattedMessage id="settings.token" />:
</label>
<div className="input-group text-secondary mb-0 h6">
<input id="sindriaccesstoken" data-id="settingsTabSindriAccessToken" type="password" className="form-control" onChange={(e) => handleChangeTokenState(e)} value={sindriToken} />
<div className="input-group-append">
<CopyToClipboard tip={intl.formatMessage({id: 'settings.copy'})} content={sindriToken} data-id="copyToClipboardCopyIcon" className="far fa-copy ml-1 p-2 mt-1" direction={'top'} />
</div>
</div>
</div>
<div>
<div className="text-secondary mb-0 h6">
<div className="d-flex justify-content-end pt-2">
<input className="btn btn-sm btn-primary ml-2" id="savesindritoken" data-id="settingsTabSaveSindriToken" onClick={saveSindriToken} value={intl.formatMessage({id: 'settings.save'})} type="button"></input>
<CustomTooltip tooltipText={<FormattedMessage id="settings.deleteSindriCredentials" />} tooltipClasses="text-nowrap" tooltipId="removesindritokenTooltip" placement="top-start">
<button className="btn btn-sm btn-secondary ml-2" id="removesindritoken" data-id="settingsTabRemoveSindriToken" onClick={removeToken}>
<FormattedMessage id="settings.remove" />
</button>
</CustomTooltip>
</div>
</div>
</div>
</div>
</div>
)
}

@ -23,3 +23,16 @@ export interface EtherscanSettingsProps {
setUnpersistedProperty: (key: string, value: string) => void setUnpersistedProperty: (key: string, value: string) => void
} }
} }
export interface SindriSettingsProps {
saveToken: (sindriToken: string) => void,
removeToken: () => void,
config: {
exists: (key: string) => boolean,
get: (key: string) => string,
set: (key: string, content: string) => void,
clear: () => void,
getUnpersistedProperty: (key: string) => void,
setUnpersistedProperty: (key: string, value: string) => void
}
}

@ -42,7 +42,7 @@ import { ROOT_PATH, slitherYml, solTestYml, tsSolTestYml } from '../utils/consta
import { IndexedDBStorage } from '../../../../../../apps/remix-ide/src/app/files/filesystems/indexedDB' import { IndexedDBStorage } from '../../../../../../apps/remix-ide/src/app/files/filesystems/indexedDB'
import { getUncommittedFiles } from '../utils/gitStatusFilter' import { getUncommittedFiles } from '../utils/gitStatusFilter'
import { AppModal, ModalTypes } from '@remix-ui/app' import { AppModal, ModalTypes } from '@remix-ui/app'
import { contractDeployerScripts, etherscanScripts } from '@remix-project/remix-ws-templates' import * as templates from '@remix-project/remix-ws-templates'
declare global { declare global {
interface Window { interface Window {
@ -869,14 +869,10 @@ export const createSlitherGithubAction = async () => {
plugin.call('fileManager', 'open', path) plugin.call('fileManager', 'open', path)
} }
const scriptsRef = {
deployer: contractDeployerScripts,
etherscan: etherscanScripts,
}
export const createHelperScripts = async (script: string) => { export const createHelperScripts = async (script: string) => {
if (!scriptsRef[script]) return if (!templates[script]) return
await scriptsRef[script](plugin) await templates[script](plugin)
plugin.call('notification', 'toast', 'scripts added in the "scripts" folder') plugin.call('notification', 'toast', `'${script}' added to the workspace.`)
} }
export const updateGitSubmodules = async () => { export const updateGitSubmodules = async () => {

@ -169,7 +169,7 @@ export function HamburgerMenu(props: HamburgerMenuProps) {
fa="fa-kit fa-ts-logo" fa="fa-kit fa-ts-logo"
hideOption={hideWorkspaceOptions || hideFileOperations} hideOption={hideWorkspaceOptions || hideFileOperations}
actionOnClick={() => { actionOnClick={() => {
props.addHelperScripts('etherscan') props.addHelperScripts('etherscanScripts')
props.hideIconsMenu(!showIconsMenu) props.hideIconsMenu(!showIconsMenu)
}} }}
platforms={[appPlatformTypes.web, appPlatformTypes.desktop]} platforms={[appPlatformTypes.web, appPlatformTypes.desktop]}
@ -179,7 +179,17 @@ export function HamburgerMenu(props: HamburgerMenuProps) {
fa="fa-kit fa-ts-logo" fa="fa-kit fa-ts-logo"
hideOption={hideWorkspaceOptions || hideFileOperations} hideOption={hideWorkspaceOptions || hideFileOperations}
actionOnClick={() => { actionOnClick={() => {
props.addHelperScripts('deployer') props.addHelperScripts('contractDeployerScripts')
props.hideIconsMenu(!showIconsMenu)
}}
platforms={[appPlatformTypes.web, appPlatformTypes.desktop]}
></HamburgerMenuItem>
<HamburgerMenuItem
kind="addscriptsindri"
fa="fa-kit fa-ts-logo"
hideOption={hideWorkspaceOptions || hideFileOperations}
actionOnClick={() => {
props.addHelperScripts('sindriScripts')
props.hideIconsMenu(!showIconsMenu) props.hideIconsMenu(!showIconsMenu)
}} }}
platforms={[appPlatformTypes.web, appPlatformTypes.desktop]} platforms={[appPlatformTypes.web, appPlatformTypes.desktop]}

@ -12,4 +12,5 @@ export { default as rln } from './templates/rln'
export { contractDeployerScripts } from './script-templates/contract-deployer' export { contractDeployerScripts } from './script-templates/contract-deployer'
export { etherscanScripts } from './script-templates/etherscan' export { etherscanScripts } from './script-templates/etherscan'
export { sindriScripts } from './script-templates/sindri'

@ -0,0 +1,4 @@
# Files to exclude from Sindri circuit uploads (uses `.gitignore` syntax).
/.deps/
/scripts/
/templates/

@ -0,0 +1,120 @@
# Sindri Scripts
The `sindri/scripts/` directory contains scripts for compiling circuits and generating Zero-Knowledge Proofs remotely using [Sindri](https://sindri.app).
This README file will walk you through all of the steps necessary to compile your circuit and generate proofs.
As you read through it, you might also find it helpful to refer to external documentation:
- [Circom 2 Documentation](https://docs.circom.io/)
- [Sindri Documentation](https://sindri.app/docs/)
## Add the Sindri ZK Scripts
If you're seeing this README, then you've probably already figured out this step on your own!
You can add the Sindri ZK scripts and related project files to your workspace by clicking the hamburger icon in the upper left corner of the **File Explorer** and selecting **Add Sindri ZK scripts**.
This will automatically add this README file, several TypeScript files, a `sindri.json` project manifest, and a `.sindriignore` file to your workspace.
We'll cover these files in more detail below.
## API Key
To interact with the Sindri API, you will first need to create a Sindri account, generate an API key, and add it to your Remix IDE settings.
This only needs to be done once, your credentials will be shared across all of your current and future workspaces once you've added your API key.
1. Visit [The Sindri Homepage](https://sindri.app/) and request a demo to create your account.
2. Follow the instructions in the [Access Management](https://sindri.app/docs/topic-guides/access-management/#api-key-creation-and-management) documentation to generate an API key.
3. Open the **Settings** panel by clicking on the gear icon at the very bottom of the icon panel on the left side of the Remix IDE (see the [Remix IDE Settings](https://remix-ide.readthedocs.io/en/latest/settings.html) documentation if you're having trouble finding it.
4. Navigate to the **Sindri Credentials** section of the **Settings** panel, enter your Sindri API key under **Token**, and click the **Save** button.
## Customize `sindri.json` _(Optional)_
A `sindri.json` file was added to the root of your workspace, and automatically customized to fit your project layout.
This file is the **Sindri Manifest** and is required for all projects deployed to Sindri.
It's also used by the [Sindri CLI](https://github.com/Sindri-Labs/sindri-js) for local circuit operations which don't require a Sindri account.
If the automatic customization missed something, or if you'd like to make further customizations, then you'll need to edit this file yourself.
When editing `sindri.json` in the Remix IDE, you should get in-editor diagnostics and documentation about the format of the file.
You can mouse over the different properties and their values to view their documentation and any potential errors with the values.
The fields that you're most likely to want to customize are:
- `name` - This is a unique project identifier for your circuit.
You can think of it as being analogous to a GitHub project name or a DockerHub image name.
Every time you compile a circuit with Sindri, the compiled circuit will be associated with the project and one or more tags (`latest` by default).
We guessed this based on your workspace name, but you can change this to something else if you don't like that name.
- `circuitPath` - This defines the entrypoint for a Circom circuit (_i.e_ the `.circom` source file which contains your `main` component).
We did our best to guess this as well, but you'll need to update this manually if you refactor your circuit files or the wrong entrypoint was detected.
## Customize `.sindriignore` _(Optional)_
A `.sindriignore` file was automatically added to the root of your workspace when you added the ZK scripts.
This file can be used to exclude files and directories from your circuit package when deploying it to Sindri, and it follows the [`.gitignore` Format](https://git-scm.com/docs/gitignore).
The generated file includes some sane defaults, but you can feel free to customize it as you see fit.
This is particularly useful if you want to exclude files that contain sensitive information like credentials or secret keys.
Excluding irrelevant files will also have a positive impact on the performance of compiling and generating proofs because less data needs to be transferred.
## Compile the Circuit
The `scripts/sindri/run_compile.ts` script can be used to compile a new version of your circuit.
To run it, you can open the script from the **File Explorer**, then either click the play button icon in the upper left corner of the editor panel or press `CTRL + SHIFT + S`.
After running it, you should see something like
```
Compiling circuit "multiplier2"...
Circuit compiled successfully, circuit id: f593a775-723c-4c57-8d75-196aa8c22aa0
```
indicating that the circuit compiled successfully.
By default, this newly compiled circuit will be assigned a tag of `latest` and replace any previous circuit with that tag.
If you would like to use alternative tags, you can modify the script to pass an array of tags to the `compile()` function call in the script.
We recommend starting out with the default of `latest` as you're getting started, and then moving towards tighter tag management once you're closer to productionizing your circuit.
## Generate a Proof
Once you've compiled your circuit, you're almost ready to use the `scripts/sindri/run_prove.ts` script to generate a proof.
You'll first need to modify this file to pass in the input signals that you would like to generate a proof for when calling `prove(signals)` (see Circom's [Signals & Variables](https://docs.circom.io/circom-language/signals/) documentation if you need a refresher on circuit signals).
Towards the top of the script, you'll see where the `signals` variable is defined.
```typescript
const signals: {[name: string]: number | string} = {}
```
You'll need to modify this object to include a map from your circuit's signal names to the values you would like to generate a proof with.
If the signals aren't set correctly, then you'll get an error when you try to generate a proof, so make sure you don't skip this step.
While the `scripts/sindri/run_prove.ts` script is open in the editor, you can click the play icon or press `CTRL + SHIFT + S` to run the script.
If proof generation is successful, you should see an output like this.
```
Proving circuit "multiplier2"...
Proof generated successfully, proof id: 8c457574-99cd-4042-a598-0514ee83ea28
Proof:
{
"pi_a": [
"6067132175610399619979395342154926888794311761598436094198046058376456187483",
"12601521866404307402196517712981356634013036480344794909770435164414221099781",
"1"
],
"pi_b": [
[
"4834637265002576910303922443793957462767968914058257618737938706178679757759",
"9112483377654285712375849001111771826297690938023943203596780715231459796539"
],
[
"10769047435756102293620257834720404252539733306406452142820929656229947907912",
"13357635314682194333795190402038393873064494630028726306217246944693858036728"
],
[
"1",
"0"
]
],
"pi_c": [
"14880777940364750676687351211095959384403767617776048892575602333362895582325",
"16991336882479219442414889002846661737157620156103416755440340170710340617407",
"1"
],
"protocol": "groth16"
}
```
You can either manually copy the proof to wherever you would like to use it, or modify the script to save it to a dedicated location.

@ -0,0 +1,94 @@
const getWorkspaceFilesByPath = async (plugin: any, pathRegex: RegExp | null = null): Promise<{[path: string]: File}> => {
const filesByPath: {[path: string]: File} = {}
interface Workspace {
children?: Workspace
content?: string
}
const workspace: Workspace = await plugin.call('fileManager', 'copyFolderToJson', '/')
const childQueue: Array<[string, Workspace]> = Object.entries(workspace)
while (childQueue.length > 0) {
const [path, child] = childQueue.pop()
if ('content' in child && (pathRegex === null || pathRegex.test(path))) {
filesByPath[path] = new File([child.content], path)
}
if ('children' in child) {
childQueue.push(...Object.entries(child.children))
}
}
return filesByPath
}
export const sindriScripts = async (plugin: any) => {
// Load in all of the Sindri or circuit-related files in the workspace.
const existingFilesByPath = await getWorkspaceFilesByPath(plugin, /sindri|\.circom$/i)
const writeIfNotExists = async (path: string, content: string) => {
if (!(path in existingFilesByPath)) {
await plugin.call('fileManager', 'writeFile', path, content)
}
}
// Write out all of the static files if they don't exist.
// @ts-ignore
await writeIfNotExists('.sindriignore', (await import('!!raw-loader!./.sindriignore')).default)
// @ts-ignore
await writeIfNotExists('scripts/sindri/README.md', (await import('!!raw-loader!./README.md')).default)
// @ts-ignore
await writeIfNotExists('scripts/sindri/run_compile.ts', (await import('!!raw-loader!./run_compile.ts')).default)
// @ts-ignore
await writeIfNotExists('scripts/sindri/run_prove.ts', (await import('!!raw-loader!./run_prove.ts')).default)
// @ts-ignore
await writeIfNotExists('scripts/sindri/utils.ts', (await import('!!raw-loader!./utils.ts')).default)
// Only write out the `sindri.json` file if it doesn't already exist.
if (!('sindri.json' in existingFilesByPath)) {
// @ts-ignore
const sindriManifest = (await import('./sindri.json')).default
// TODO: We can try to infer the circuit framework here from the project contents.
// For now, we only support Circom.
// Infer manifest properties from the existing files in the workspace.
if (sindriManifest.circuitType === 'circom') {
// Try to find the best `.circom` source file to use as the main component.
// First, we limit ourselves to `.circom` files.
const circomPathsAndContents = await Promise.all(
Object.entries(existingFilesByPath)
.filter(([path]) => /\.circom$/i.test(path))
.map(async ([path, file]) => [path, await file.text()])
)
// Now we apply some heuristics to find the "best" file.
const circomCircuitPath =
circomPathsAndContents
.map(([path, content]) => ({
content,
hasMainComponent: !!/^[ \t\f]*component[ \t\f]+main[^\n\r]*;[ \t\f]*$/m.test(content),
// These files are the entrypoints to the Remix Circom templates, so we give them a boost if there are multiple main components.
isTemplateEntrypoint: !!['calculate_hash.circom', 'rln.circom', 'semaphore.circom'].includes(path.split('/').pop() ?? ''),
path,
}))
.sort((a, b) => {
if (a.hasMainComponent !== b.hasMainComponent) return +b.hasMainComponent - +a.hasMainComponent
if (a.isTemplateEntrypoint !== b.isTemplateEntrypoint) return +b.isTemplateEntrypoint - +a.isTemplateEntrypoint
return a.path.localeCompare(b.path)
})
.map(({path}) => path)[0] || './circuit.circom'
sindriManifest.circuitPath = circomCircuitPath
}
// Derive the circuit name from the workspace name.
const {name: workspaceName} = await plugin.call('filePanel', 'getCurrentWorkspace')
sindriManifest.name =
workspaceName
.replace(/\s*-+\s*\d*$/, '')
.replace(/[^a-zA-Z0-9]+/g, '-')
.replace(/^[^a-zA-Z]+/, '')
.toLowerCase() || `my-${sindriManifest.circuitType}-circuit`
// Write out the modified manifest file.
writeIfNotExists('sindri.json', JSON.stringify(sindriManifest, null, 2))
}
// Open the README file in the editor.
await plugin.call('doc-viewer' as any, 'viewDocs', ["scripts/sindri/README.md"])
plugin.call('tabs' as any, 'focus', 'doc-viewer')
}

@ -0,0 +1,7 @@
import {compile} from './utils'
const main = async () => {
const circuit = await compile()
}
main()

@ -0,0 +1,15 @@
import {prove} from './utils'
// You must modify the input signals to include the data you're trying to generate a proof for.
const signals: {[name: string]: number | string} = {}
const main = async () => {
if (Object.keys(signals).length === 0) {
console.error("You must modify the input signals to include the data you're trying to generate a proof for.")
return
}
const proofResponse = await prove(signals)
console.log('Proof:\n', JSON.stringify(proofResponse.proof, null, 2))
}
main()

@ -0,0 +1,9 @@
{
"$schema": "https://sindri.app/api/v1/sindri-manifest-schema.json",
"name": "circuit_name",
"circuitPath": "./circuits/circuit.circom",
"circuitType": "circom",
"curve": "bn254",
"provingScheme": "groth16",
"witnessCompiler": "c++"
}

@ -0,0 +1,139 @@
import sindriClient from 'sindri'
import type {CircuitInfoResponse, ProofInfoResponse} from 'sindri'
sindriClient.logLevel = 'info'
const authorize = async () => {
try {
const apiKey = await remix.call('settings', 'get', 'settings/sindri-access-token')
if (!apiKey) {
throw new Error('Missing API key.')
}
sindriClient.authorize({apiKey})
} catch {
const message = 'No Sindri API key found. Please click the gear in the lower left corner to open the settings page, and add your API key under "Sindri Credentials".'
await remix.call('notification', 'toast', message)
throw new Error(message)
}
}
const getSindriManifest = async () => {
const sindriJson = await remix.call('fileManager', 'readFile', `sindri.json`)
return JSON.parse(sindriJson)
}
const normalizePath = (path: string): string => {
while (path.startsWith('/') || path.startsWith('./')) {
path = path.replace(/^(\.\/|\/)/, '')
}
return path
}
/**
* Compile the circuit.
*
* @param {string | string[] | null} tags - The tag or tags to use when compiling the circuit.
* @returns {CircuitInfoResponse} compiled circuit
*/
export const compile = async (tags: string | string[] | null = ['latest']): CircuitInfoResponse => {
await authorize()
const sindriManifest = await getSindriManifest()
// Create a map from file paths to `File` objects for (almost) all files in the workspace.
// We exclude `.deps/` files because these are resolved to more intuitive locations so they can
// be used by the circuit without specifying a complex import path. We'll merge the dependencies
// into the files at their expected import paths in a later step.
const excludeRegex = /^\.deps\//
const filesByPath: {[path: string]: File} = {}
interface Workspace {
children?: Workspace
content?: string
}
const workspace: Workspace = await remix.call('fileManager', 'copyFolderToJson', '/')
const childQueue: Array<[string, Workspace]> = Object.entries(workspace)
while (childQueue.length > 0) {
const [path, child] = childQueue.pop()
if ('content' in child && !excludeRegex.test(path)) {
filesByPath[path] = new File([child.content], path)
}
if ('children' in child) {
childQueue.push(...Object.entries(child.children))
}
}
// Merge any of the circuit's resolved dependencies into the files at their expected import paths.
if (sindriManifest.circuitType === 'circom') {
const circuitPath = normalizePath(sindriManifest.circuitPath || 'circuit.circom')
let circuitContent: string
try {
circuitContent = await remix.call('fileManager', 'readFile', circuitPath)
} catch (error) {
console.error(`No circuit file found at "${circuitPath}", try setting "circuitPath" in "sindri.json".`)
}
const dependencies: {[path: string]: string} = await remix.call('circuit-compiler' as any, 'resolveDependencies', circuitPath, circuitContent)
Object.entries(dependencies).forEach(([rawPath, rawContent]) => {
// Convert absolute file paths to paths relative to the project root.
const path = normalizePath(rawPath)
// Removes any leading `/`s from Circom `include` paths to make them relative to the root.
const content = path.endsWith('.circom') ? rawContent.replace(/^\s*include\s+"\/+([^"]+)"\s*;\s*$/gm, 'include "$1";') : rawContent
filesByPath[path] = new File([content], path)
})
}
console.log(`Compiling circuit "${sindriManifest.name}"...`)
const files = Object.values(filesByPath)
try {
const circuitResponse = await sindriClient.createCircuit(files, tags)
if (circuitResponse.status === 'Ready') {
console.log(`Circuit compiled successfully, circuit id: ${circuitResponse.circuit_id}`)
} else {
console.error('Circuit compilation failed:', circuitResponse.error || 'Unknown error')
}
return circuitResponse
} catch (error) {
if ('status' in error && error.status === 401) {
const message = 'Sindri API key authentication failed, please check that your key is correct in the settings.'
console.error(message)
throw new Error(message)
} else {
console.error('Unknown error occurred.')
throw error
}
}
}
/**
* Generate a proof against the circuit.
*
* @param {Object} signals - Input signals for the circuit.
* @returns {ProofInfoResponse} The generated proof.
*/
export const prove = async (signals: {[id: string]: number | string}): ProofInfoResponse => {
await authorize()
const sindriManifest = await getSindriManifest()
const circuitName = sindriManifest.name
console.log(`Proving circuit "${circuitName}"...`)
try {
const proofResponse = await sindriClient.proveCircuit(circuitName, JSON.stringify(signals))
if (proofResponse.status === 'Ready') {
console.log(`Proof generated successfully, proof id: ${proofResponse.proof_id}`)
} else {
console.error('Proof generation failed:', proofResponse.error || 'Unknown error')
}
return proofResponse
} catch (error) {
if ('status' in error && error.status === 401) {
const message = 'Sindri API key authentication failed, please check that your key is correct in the settings.'
console.error(message)
throw new Error(message)
} else if ('status' in error && error.status === 404) {
const message = `No compiled circuit "${circuitName}" found, have you successfully compiled the circuit?`
console.error(message)
throw new Error(message)
} else {
console.error('Unknown error occurred.')
throw error
}
}
}

@ -8112,6 +8112,14 @@
"npm:react-intl" "npm:react-intl"
] ]
}, },
{
"file": "libs/remix-ui/settings/src/lib/sindri-settings.tsx",
"hash": "f25473fcac87f3e7aa8be1828ea446b1b261fc66",
"deps": [
"npm:react",
"npm:react-intl"
]
},
{ {
"file": "libs/remix-ui/settings/src/lib/remix-ui-settings.css", "file": "libs/remix-ui/settings/src/lib/remix-ui-settings.css",
"hash": "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391" "hash": "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"

Loading…
Cancel
Save