Install circom via cli

pull/5228/head
ioedeveloper 7 months ago committed by bunsenstraat
parent 1a5686e33c
commit 7b3c51f661
  1. 1
      .gitignore
  2. 18
      apps/circuit-compiler/src/app/services/circomPluginClient.ts
  3. 6
      apps/remix-ide/src/app.js
  4. 12
      apps/remix-ide/src/app/plugins/electron/circomElectronPlugin.ts
  5. 2
      apps/remix-ide/src/remixAppManager.js
  6. 1
      apps/remix-ide/src/remixEngine.js
  7. 20
      apps/remixdesktop/package.json
  8. 3
      apps/remixdesktop/src/engine.ts
  9. 52
      apps/remixdesktop/src/plugins/circomElectronBasePlugin.ts
  10. 2
      apps/remixdesktop/src/preload.ts
  11. 55
      apps/remixdesktop/src/tools/circom.ts
  12. 83
      apps/remixdesktop/yarn.lock

1
.gitignore vendored

@ -68,4 +68,5 @@ apps/remix-ide/src/assets/esbuild.wasm
apps/remixdesktop/build*
apps/remixdesktop/reports
apps/remixdesktop/logs/
apps/remixdesktop/bin/
logs

@ -8,7 +8,8 @@ import * as compilerV217 from 'circom_wasm/v2.1.7'
import * as compilerV216 from 'circom_wasm/v2.1.6'
import * as compilerV215 from 'circom_wasm/v2.1.5'
import { extractNameFromKey, extractParentFromKey } from '@remix-ui/helper'
import { CompilationConfig, CompilerReport, PrimeValue, ResolverOutput } from '../types'
import { CompilationConfig, CompilerReport, PrimeValue } from '../types'
import isElectron from 'is-electron'
export class CircomPluginClient extends PluginClient {
public internalEvents: EventManager
@ -61,6 +62,10 @@ export class CircomPluginClient extends PluginClient {
}
async parse(path: string, fileContent?: string): Promise<[CompilerReport[], Record<string, string>]> {
if (isElectron()) {
// @ts-ignore
return await this.call('circom', 'parse', path)
} else {
if (!fileContent) {
// @ts-ignore
fileContent = await this.call('fileManager', 'readFile', path)
@ -133,8 +138,13 @@ export class CircomPluginClient extends PluginClient {
throw new Error(e)
}
}
}
async compile(path: string, compilationConfig?: CompilationConfig): Promise<void> {
if (isElectron()) {
// @ts-ignore
return await this.call('circom', 'compile', path)
} else {
this.internalEvents.emit('circuit_compiling_start')
this.emit('statusChanged', { key: 'loading', title: 'Compiling...', type: 'info' })
// @ts-ignore
@ -195,8 +205,13 @@ export class CircomPluginClient extends PluginClient {
this.call('terminal', 'log', { type: 'typewritersuccess', value: 'Everything went okay' })
}
}
}
async generateR1cs (path: string, compilationConfig?: CompilationConfig): Promise<void> {
if (isElectron()) {
// @ts-ignore
return await this.call('circom', 'generateR1cs', path)
} else {
const [parseErrors, filePathToId] = await this.parse(path)
if (parseErrors && (parseErrors.length > 0)) {
@ -236,6 +251,7 @@ export class CircomPluginClient extends PluginClient {
this.call('terminal', 'log', { type: 'typewritersuccess', value: 'Everything went okay' })
}
}
}
async computeWitness (input: string): Promise<Uint8Array> {
this.internalEvents.emit('circuit_computing_witness_start')

@ -66,7 +66,7 @@ import { FoundryHandle } from './app/files/foundry-handle'
import { FoundryHandleDesktop } from './app/plugins/electron/foundryPlugin'
import { HardhatHandle } from './app/files/hardhat-handle'
import { HardhatHandleDesktop } from './app/plugins/electron/hardhatPlugin'
import { circomPlugin } from './app/plugins/electron/circomElectronPlugin'
import { GitPlugin } from './app/plugins/git'
import { Matomo } from './app/plugins/matomo'
@ -419,6 +419,8 @@ class AppComponent {
this.engine.register([xterm])
const ripgrep = new ripgrepPlugin()
this.engine.register([ripgrep])
const circom = new circomPlugin()
this.engine.register([circom])
const appUpdater = new appUpdaterPlugin()
this.engine.register([appUpdater])
const remixAIDesktop = new remixAIDesktopPlugin()
@ -565,7 +567,7 @@ class AppComponent {
await this.appManager.activatePlugin(['solidity-script', 'remix-templates'])
if (isElectron()) {
await this.appManager.activatePlugin(['isogit', 'electronconfig', 'electronTemplates', 'xterm', 'ripgrep', 'appUpdater', 'slither', 'foundry', 'hardhat']) // 'remixAID'
await this.appManager.activatePlugin(['isogit', 'electronconfig', 'electronTemplates', 'xterm', 'ripgrep', 'appUpdater', 'slither', 'foundry', 'hardhat', 'circom']) // 'remixAID'
}
this.appManager.on(

@ -0,0 +1,12 @@
import { ElectronPlugin } from '@remixproject/engine-electron'
export class circomPlugin extends ElectronPlugin {
constructor() {
super({
displayName: 'circom',
name: 'circom',
description: 'circom language compiler',
})
this.methods = []
}
}

@ -175,7 +175,7 @@ export class RemixAppManager extends PluginManager {
this.pluginsDirectory = 'https://raw.githubusercontent.com/ethereum/remix-plugins-directory/master/build/metadata.json'
this.pluginLoader = new PluginLoader()
if (Registry.getInstance().get('platform').api.isDesktop()) {
requiredModules = [...requiredModules, 'fs', 'electronTemplates', 'isogit', 'remix-templates', 'electronconfig', 'xterm', 'compilerloader', 'ripgrep', 'slither', 'remixAID']
requiredModules = [...requiredModules, 'fs', 'electronTemplates', 'isogit', 'remix-templates', 'electronconfig', 'xterm', 'compilerloader', 'ripgrep', 'slither', 'remixAID', 'circom']
}
}

@ -30,6 +30,7 @@ export class RemixEngine extends Engine {
if (name === 'remixAI') return { queueTimeout: 60000 * 20 }
if (name === 'cookbookdev') return { queueTimeout: 60000 * 3 }
if (name === 'contentImport') return { queueTimeout: 60000 * 3 }
if (name === 'circom') return { queueTimeout: 60000 * 4 }
return { queueTimeout: 10000 }
}

@ -21,8 +21,25 @@
},
"homepage": "https://github.com/ethereum/remix-project#readme",
"appId": "org.ethereum.remixdesktop",
"build": {
"files": [
"build/**/*"
],
"mac": {
"category": "public.app-category.productivity"
"category": "public.app-category.productivity",
"icon": "assets/icon.png",
"darkModeSupport": true,
"hardenedRuntime": true,
"gatekeeperAssess": false,
"entitlements": "entitlements.mac.plist",
"entitlementsInherit": "entitlements.mac.plist",
"extraResources": [
{
"from": "bin/circom-macos-amd64",
"to": "bin/circom-macos-amd64"
}
]
}
},
"scripts": {
"start:dev": "yarn webpack --config webpack.config.js && electron --inspect=5858 .",
@ -75,6 +92,7 @@
"axios": "^1.7.4",
"byline": "^5.0.0",
"chokidar": "^3.5.3",
"dockerode": "^4.0.2",
"electron-updater": "^6.1.8",
"express": "^4.20.0",
"isomorphic-git": "^1.24.2",

@ -14,6 +14,7 @@ import { AppUpdaterPlugin } from './plugins/appUpdater';
import { RemixAIDesktopPlugin } from './plugins/remixAIDektop';
import { FoundryPlugin } from './plugins/foundryPlugin';
import { HardhatPlugin } from './plugins/hardhatPlugin';
import { CircomElectronPlugin } from './plugins/circomElectronBasePlugin';
import { isE2E } from './main';
const engine = new Engine()
@ -30,6 +31,7 @@ const appUpdaterPlugin = new AppUpdaterPlugin()
const foundryPlugin = new FoundryPlugin()
const hardhatPlugin = new HardhatPlugin()
const remixAIDesktopPlugin = new RemixAIDesktopPlugin()
const circomPlugin = new CircomElectronPlugin()
engine.register(appManager)
engine.register(fsPlugin)
@ -44,6 +46,7 @@ engine.register(foundryPlugin)
engine.register(appUpdaterPlugin)
engine.register(hardhatPlugin)
engine.register(remixAIDesktopPlugin)
engine.register(circomPlugin)
appManager.activatePlugin('electronconfig')
appManager.activatePlugin('fs')

@ -0,0 +1,52 @@
import { ElectronBasePlugin, ElectronBasePluginClient } from "@remixproject/plugin-electron"
import { Profile } from "@remixproject/plugin-utils"
import { circomCli } from "../tools/circom"
const profile: Profile = {
displayName: 'circom',
name: 'circom',
description: 'Circom language compiler'
}
export class CircomElectronPlugin extends ElectronBasePlugin {
clients: CircomElectronPluginClient[] = []
constructor() {
super(profile, clientProfile, CircomElectronPluginClient)
this.methods = [...super.methods]
}
async onActivation(): Promise<void> {
console.log('activating to exec')
if (!(await circomCli.isCargoInstalled())) await circomCli.installRustup()
if (!(await circomCli.isCircomInstalled())) await circomCli.installCircom()
}
}
const clientProfile: Profile = {
name: 'circom',
displayName: 'circom',
description: 'Circom Language Compiler',
methods: ['parse', 'compile', 'generateR1cs']
}
class CircomElectronPluginClient extends ElectronBasePluginClient {
circomIsInstalled: boolean = false
constructor(webContentsId: number, profile: Profile) {
super(webContentsId, profile)
this.onload()
}
async compile() {
console.log('compiling circom circuit...')
}
parse(): void {
console.log('parsing circom electron plugin...')
}
generateR1cs(): void {
console.log('generating r1cs circom electron plugin...')
}
}

@ -6,7 +6,7 @@ console.log('preload.ts', new Date().toLocaleTimeString())
/* preload script needs statically defined API for each plugin */
const exposedPLugins = ['fs', 'git', 'xterm', 'isogit', 'electronconfig', 'electronTemplates', 'ripgrep', 'compilerloader', 'appUpdater', 'slither', 'foundry', 'hardhat', 'remixAID']
const exposedPLugins = ['fs', 'git', 'xterm', 'isogit', 'electronconfig', 'electronTemplates', 'ripgrep', 'compilerloader', 'appUpdater', 'slither', 'foundry', 'hardhat', 'remixAID', 'circom']
let webContentsId: number | undefined

@ -0,0 +1,55 @@
import { app } from 'electron'
import { promisify } from 'util'
import { exec } from 'child_process'
import { gitProxy } from './git'
import path from 'path'
import { existsSync } from 'fs'
const execAsync = promisify(exec)
export const circomCli = {
async installRustup () {
try {
console.log('installing rustup')
const { stdout } = await execAsync(`curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y`)
console.log('stdout: ', stdout)
} catch (e) {
console.error(e)
}
},
async installCircom () {
try {
const appPath = app.getAppPath()
const targetPath = path.join(appPath, 'bin')
console.log('cloning circom repo to ' + targetPath)
if (!existsSync(`${targetPath}/circom`)) await gitProxy.clone('https://github.com/iden3/circom.git', targetPath)
console.log('builing circom with cargo')
await execAsync(`cd ${targetPath}/circom && cargo build --release && cargo install --path circom`)
} catch (e) {
console.error(e)
}
},
async isCircomInstalled () {
try {
await execAsync(`circom --version`)
return true
} catch (e) {
return false
}
},
async isCargoInstalled () {
try {
await execAsync(`cargo version`)
return true
} catch (e) {
return false
}
}
}

@ -14,6 +14,11 @@
dependencies:
regenerator-runtime "^0.14.0"
"@balena/dockerignore@^1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@balena/dockerignore/-/dockerignore-1.0.2.tgz#9ffe4726915251e8eb69f44ef3547e0da2c03e0d"
integrity sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==
"@develar/schema-utils@~2.6.5":
version "2.6.5"
resolved "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz"
@ -1724,7 +1729,7 @@ arrify@^2.0.1:
resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa"
integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==
asn1@~0.2.3:
asn1@^0.2.6, asn1@~0.2.3:
version "0.2.6"
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d"
integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==
@ -1853,7 +1858,7 @@ base64-js@^1.3.1, base64-js@^1.5.1:
resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
bcrypt-pbkdf@^1.0.0:
bcrypt-pbkdf@^1.0.0, bcrypt-pbkdf@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
integrity sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==
@ -2088,6 +2093,11 @@ bufferutil@^4.0.1:
dependencies:
node-gyp-build "^4.3.0"
buildcheck@~0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/buildcheck/-/buildcheck-0.0.6.tgz#89aa6e417cfd1e2196e3f8fe915eb709d2fe4238"
integrity sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A==
builder-util-runtime@9.2.3:
version "9.2.3"
resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-9.2.3.tgz#0a82c7aca8eadef46d67b353c638f052c206b83c"
@ -2234,7 +2244,7 @@ chokidar@3.5.3, chokidar@^3.5.3:
optionalDependencies:
fsevents "~2.3.2"
chownr@^1.1.4:
chownr@^1.1.1, chownr@^1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
@ -2491,6 +2501,14 @@ cors@^2.8.1:
object-assign "^4"
vary "^1"
cpu-features@~0.0.9:
version "0.0.10"
resolved "https://registry.yarnpkg.com/cpu-features/-/cpu-features-0.0.10.tgz#9aae536db2710c7254d7ed67cb3cbc7d29ad79c5"
integrity sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==
dependencies:
buildcheck "~0.0.6"
nan "^2.19.0"
crc-32@^1.2.0:
version "1.2.2"
resolved "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz"
@ -2790,6 +2808,25 @@ dmg-license@^1.0.11:
smart-buffer "^4.0.2"
verror "^1.10.0"
docker-modem@^5.0.3:
version "5.0.3"
resolved "https://registry.yarnpkg.com/docker-modem/-/docker-modem-5.0.3.tgz#50c06f11285289f58112b5c4c4d89824541c41d0"
integrity sha512-89zhop5YVhcPEt5FpUFGr3cDyceGhq/F9J+ZndQ4KfqNvfbJpPMfgeixFgUj5OjCYAboElqODxY5Z1EBsSa6sg==
dependencies:
debug "^4.1.1"
readable-stream "^3.5.0"
split-ca "^1.0.1"
ssh2 "^1.15.0"
dockerode@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/dockerode/-/dockerode-4.0.2.tgz#dedc8529a1db3ac46d186f5912389899bc309f7d"
integrity sha512-9wM1BVpVMFr2Pw3eJNXrYYt6DT9k0xMcsSCjtPvyQ+xa1iPg/Mo3T/gUcwI0B2cczqCeCYRPF8yFYDwtFXT0+w==
dependencies:
"@balena/dockerignore" "^1.0.2"
docker-modem "^5.0.3"
tar-fs "~2.0.1"
dom-walk@^0.1.0:
version "0.1.2"
resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.2.tgz#0c548bef048f4d1f2a97249002236060daa3fd84"
@ -5148,6 +5185,11 @@ minizlib@^2.1.1:
minipass "^3.0.0"
yallist "^4.0.0"
mkdirp-classic@^0.5.2:
version "0.5.3"
resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
mkdirp-promise@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/mkdirp-promise/-/mkdirp-promise-5.0.1.tgz#e9b8f68e552c68a9c1713b84883f7a1dd039b8a1"
@ -5277,6 +5319,11 @@ nan@^2.17.0:
resolved "https://registry.yarnpkg.com/nan/-/nan-2.20.0.tgz#08c5ea813dd54ed16e5bd6505bf42af4f7838ca3"
integrity sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==
nan@^2.18.0, nan@^2.19.0:
version "2.20.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.20.0.tgz#08c5ea813dd54ed16e5bd6505bf42af4f7838ca3"
integrity sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==
nano-json-stream-parser@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/nano-json-stream-parser/-/nano-json-stream-parser-0.1.2.tgz#0cc8f6d0e2b622b479c40d499c46d64b755c6f5f"
@ -5841,7 +5888,7 @@ read-config-file@6.3.2:
json5 "^2.2.0"
lazy-val "^1.0.4"
readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0:
readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0, readable-stream@^3.6.0:
version "3.6.2"
resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz"
integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==
@ -6384,11 +6431,27 @@ source-map@^0.7.4:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656"
integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==
split-ca@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/split-ca/-/split-ca-1.0.1.tgz#6c83aff3692fa61256e0cd197e05e9de157691a6"
integrity sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==
sprintf-js@^1.1.2:
version "1.1.2"
resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz"
integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==
ssh2@^1.15.0:
version "1.15.0"
resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-1.15.0.tgz#2f998455036a7f89e0df5847efb5421748d9871b"
integrity sha512-C0PHgX4h6lBxYx7hcXwu3QWdh4tg6tZZsTfXcdvc5caW/EMxaB4H9dWsl7qk+F7LAW762hp8VbXOX7x4xUYvEw==
dependencies:
asn1 "^0.2.6"
bcrypt-pbkdf "^1.0.2"
optionalDependencies:
cpu-features "~0.0.9"
nan "^2.18.0"
sshpk@^1.7.0:
version "1.18.0"
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.18.0.tgz#1663e55cddf4d688b86a46b77f0d5fe363aba028"
@ -6600,6 +6663,16 @@ tapable@^2.1.1, tapable@^2.2.0:
resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0"
integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==
tar-fs@~2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.0.1.tgz#e44086c1c60d31a4f0cf893b1c4e155dabfae9e2"
integrity sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA==
dependencies:
chownr "^1.1.1"
mkdirp-classic "^0.5.2"
pump "^3.0.0"
tar-stream "^2.0.0"
tar-stream@3.1.6:
version "3.1.6"
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-3.1.6.tgz#6520607b55a06f4a2e2e04db360ba7d338cc5bab"
@ -6609,7 +6682,7 @@ tar-stream@3.1.6:
fast-fifo "^1.2.0"
streamx "^2.15.0"
tar-stream@^2.1.0:
tar-stream@^2.0.0, tar-stream@^2.1.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287"
integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==

Loading…
Cancel
Save