https://0xparc.org/blog/zkrepl

https://zkrepl.dev/

Try out zkREPL here! Also see the repository, and check out circom2 on npm.

In the Paleolithic age of computing, computers were great electromechanical beasts that could execute a few dozen operations a second at the cost of millions of dollars a year. Programs were developed by meticulous meditation, by plugging wires in switchboards, and by punching cards. At the time, bugs were literal insects caught nibbling on wires, occasionally causing electrical shorts which threatened to literally burn everything down.

Back then, programming a computer was an arduous activity filled with ritual and ceremony. But in the late 1950s, a new system of programming called LISP had been invented which was capable of reasoning about its own implementation—through a construct called "eval", programs could call upon their own maker to spawn new programs. Upon this, researchers came up with a simple program which would read some input from the keyboard, send it to the "eval" construct, print the result, and loop back to the start to await further input.

This one neat trick, the REPL, set into motion the democratization of programming—from something only used by nation states to steer rockets, to something used by teenagers to make video games.

ZK

In recent years we have seen the rise of a new age of computing. We have blockchains like Ethereum and Bitcoin, processing a few dozen operations a second, at the cost of millions of dollars a year in gas prices. We regularly see bugs that burn down entire protocols.

What drives this new age of computing, are new systems capable of reasoning about their own trust: from auditable centralized oracles, content-addressable storage, byzantine consensus, multi-party computation, and perhaps the most interesting one of all—zero knowledge proofs.

The current world of zero knowledge is filled ritual and ceremony. Sorcerers enlist thousands of people in their struggle to exorcise demons—to credibly dispose of toxic waste generated in a trusted setup.

But with this infancy comes the opportunity to rhyme with history, and democratize ZK.

Read

In democratizing ZK, we have to first establish a lingua franca—-a language that can be used and understood by experts and amateurs alike. To that end we were fortunate to be able to build on Circom 2.0, a simple language with syntax that should be familiar to anyone who has seen JavaScript, while exposing enough of the underlying abstraction that people can still get a grasp of the low-level circuit structure being built.

Beyond the language, we need its medium. We'd like to be able to dive into the world of ZK as easily as clicking a link in a web browser, so we built zkREPL on top of Monaco: the same text editing component that powers Visual Studio Code. But we extended it for our particular purposes—custom syntax highlighting, and tooltips with links to external includes.

In the future, it might be nice to switch to CodeMirror 6, as Monaco doesn't work particularly well on mobile devices, and we can leverage Joel Gustafson's lezer-circom project to add a proper incremental syntax parser and enable all sorts of new capabilities for exploring, navigating, and visualizing Circom code.

On a deeper level, in building systems which explicitly reason about their own trust, a ZK system is only as good as the ability for its users to be able to convince themselves that the code which allegedly underlies a given contract is actually what is running. To that end, there’s a lot more that could be done to provide a system for enabling users to verify the source code for a circuit.

Eval

Traditionally, to play with writing zero knowledge programs in Circom, you would have to first install the entire Rust toolchain and compile the compiler from source. The first step to building zkREPL was to get the Circom compiler to compile into WebAssembly so it could run portably inside a browser.

This involved a few hurdles along the way—of varying degrees of annoyance. There were a few relatively simple changes, like adapting it to support 32 bit architectures like WASM, and disabling the multithreading features which are not currently supported by Rust WASM.

Then came a few of the the more obnoxious hurdles: Circom was statically linked with an external C++ library called WABT, the WebAssembly Binary Toolkit, whose job it was to translate a text-based WAT format into the binary WASM format. The Rust WASM compiler backend doesn't support linking with modules written in different languages, but the WABT project had been separately compiled to WASM using Emscripten. To get around this, we first stripped Circom of all the code that would invoke WABT and created a wrapper script, which when asked to compile Circom to WASM, would ask launch a WABT-free Circom and ask for a WAT file and immediately feed it into another WABT module to generate the resulting WASM.

Later on, Blaine Bublitz came across the WAST library, a pure Rust implementation of the WASM binary format, and submitted a set of upstream changes to Circom to replace WABT with this pure Rust implementation, allowing us to once and for all get rid of that nasty hack.

WebAssembly is a very lightweight specification which essentially represents pure computation- it doesn't have a concept of files, time, or even an output console. This is where WASI comes in- it defines a standard environment that WASM programs can interface with in order to read files, accept input, and print to an output console. However, WASM is a new technology, and WASI even more so.

At this point our Circom/WASM implementation was ostensibly working well: it was compiling our code and generating valid WASM witness generators. However, when it came time to test things end-to-end with SnarkJS, we found that the requisite R1CS files came out to be a few bytes larger than we had expected. After a bit of sleuthing, a culprit was identified: under certain circumstances, a particular sequence of writing to a file, jumping back to a previous position, and continuing to write to that file, would provoke a bug in certain implementations of the WASI runtime that would write things to the wrong place. We ended up getting around this by vendoring and patching a version of the WASI Filesystem runtime.

At this point we had a working version of Circom 2.0 compiled to WebAssembly that would run in the browser. But beyond that, we packed it up as a NodeJS module so anyone can develop with Circom simply by running npx circom2 on their computer. Additionally, Blaine contributed a programmatic API which enabled integration with build tools such as hardhat-circom.

On top of the standard Circom language, we have also dabbled with a few extensions that go beyond the core language. We've added the ability to import code directly from external URLs, and a way to specify inputs to your witness generation program directly as a comment in the bottom of the file.

In the future, it would be interesting to extend the language further with support for custom gates in PLONK.

Print

Snarks are delicate creature; leave a single wire dangling and you may have introduced a critical bug that might burn your entire protocol to the ground. With the stakes involved, it becomes ever more critical that the environment doesn't merely print output, but interleaves it with the code in such a way that developers can truly understand every inch of the code's surface.

In zkREPL you can simply hover over signals in your source code and see a tooltip that shows the values of those signals extracted from your witness file—Look ma! No print statements!

Tools for bringing awareness to the nature of the code which is running can get much more advanced over time. For example, we might at some point be able to integrate some of the static analysis capabilities of tools like Franklyn's Ecne in order to prove the uniqueness of witnesses for circuit implementations.

In the future, we could also add new modes for visualizing and exploring circuits as block diagrams with the ability to zoom into individual blocks and see the underlying wires, as suggested by Steven Hao.

Loop

Like Bilbo Baggins' journey in the Hobbit, the story of a REPL begins and ends with going "there and back again". By returning to the start, developers can iterate on their implementations, adding features, refining algorithms, and fixing bugs. Computers have evolved quite a bit since the 1950s, and programming doesn't need to the solitary meditation that it once was. With the advent of the internet, the process of looping is no longer synchronous- it's parallel.

Efforts like implementing ECDSA and RSA in Circom are fundamentally collaborative efforts, and so zkREPL endeavors to support that by making it easy to share and collaborate on circuits. As such you can simply hit "Ctrl-S" and save a copy of your circuit as a Github Gist, and copy the URL from your browser bar to share it with a friend, or embed it into a tutorial page as an iframe.

In the future, we can shorten the loop even further with better integration with Git, or the ability to view and edit code with realtime multiplayer.

Conclusion

As 0xPARC draws from the rich mythology of Xerox PARC, this project is named in homage of that neat little trick that shifted programming from an elite ritual to a ubiquitous democratic rite.

The entire ecosystem is in its infancy, and tools like zkREPL are still quite primitive. If you'd be interested in helping out, either by implementing any of the ideas laid out above, or by contributing your own vision, come hang out on our Github Repo.

Acknowledgements

Thanks to gubsheep, Albert Ni and the rest of the 0xPARC Learning Group #1 for inspiring and subsequently testing and using this project. Thanks to Jordi Baylina and iden3 for the Circom language and SnarkJS ecosystem which powers it. Thanks to Blaine Bublitz for contributions to the WASM port of Circom. Thanks to Joel Gustafson, Lily Jordan, Steven Hao, Uma Roy and Guillermo Webster for helpful conversations about the design of the project. Also thanks to Alex Klarfeld for first introducing me to the concept of zk-SNARKs in 2017.

*在这里试用 zkREPL !另请参阅存储库,并查看npm 上的 circom2。*

在旧石器时代的计算时代,计算机是巨大的机电野兽,每秒可以执行几十次操作,每年的成本高达数百万美元。程序是通过细致的冥想、在配电盘中插入电线和打卡来开发的。当时,虫子是字面意义上的昆虫,它们会啃咬电线,偶尔会导致短路,这可能会烧毁所有东西。

那时,对计算机进行编程是一项充满仪式和仪式的艰巨活动。但是在 1950 年代后期,一种名为 LISP 的新编程系统被发明出来,它能够推理自己的实现——通过一个名为“eval”的结构,程序可以调用它们自己的制造者来产生新的程序。为此,研究人员提出了一个简单的程序,该程序可以从键盘读取一些输入,将其发送到“ eval ”构造,打印结果,然后循环回到起点等待进一步的输入。

这个巧妙的技巧 REPL 启动了编程的民主化——从仅由民族国家用来驾驶火箭的东西,到青少年用来制作视频游戏的东西。

ZK

近年来,我们见证了计算新时代的兴起。我们有像以太坊和比特币这样的区块链,每秒处理几十个操作,每年要花费数百万美元的汽油价格。我们经常看到烧毁整个协议的错误。

推动这个计算新时代的是能够推理自己的信任的新系统:来自可审计的集中式预言机、内容可寻址存储、拜占庭共识、多方计算,也许是最有趣的一个——零知识证明。

当前的零知识世界充满了仪式和仪式。巫师召集了成千上万的人进行驱魔的斗争——以可信的方式处理在受信任的环境中产生的有毒废物。

但是随着这个婴儿期的到来,有机会与历史押韵,并使 ZK 民主化。

在使 ZK 民主化的过程中,我们首先必须建立一种通用语——一种专家和业余爱好者都可以使用和理解的语言。为此,我们很幸运能够在Circom 2.0上进行构建,这是一种语法简单的语言,任何看过 JavaScript 的人都应该熟悉,同时暴露了足够多的底层抽象,人们仍然可以掌握低级正在构建的电路结构。

除了语言,我们还需要它的媒介。我们希望能够像单击 Web 浏览器中的链接一样轻松地进入 ZK 的世界,因此我们在Monaco之上构建了 zkREPL :与支持 Visual Studio Code 的文本编辑组件相同。但我们出于特定目的对其进行了扩展——自定义语法突出显示,以及带有外部包含链接的工具提示。

将来,切换到CodeMirror 6可能会更好,因为 Monaco 在移动设备上运行得不是特别好,我们可以利用 Joel Gustafson 的lezer-circom项目添加适当的增量语法解析器并启用各种新功能用于探索、导航和可视化 Circom 代码。

在更深层次上,在构建明确推理自己的信任的系统时,ZK 系统仅与其用户能够说服自己相信作为给定合约基础的代码实际上正在运行的能力一样好。为此,可以做很多工作来提供一个系统,使用户能够验证电路的源代码。

评估

传统上,要在 Circom 中编写零知识程序,您必须首先安装整个 Rust 工具链并从源代码编译编译器。构建 zkREPL 的第一步是让 Circom 编译器编译成 WebAssembly,以便它可以在浏览器中可移植地运行。

这涉及到一些障碍——不同程度的烦恼。有一些相对简单的更改,例如将其调整为支持 WASM 等 32 位架构,以及禁用Rust WASM目前不支持的多线程功能。

然后出现了一些更令人讨厌的障碍:Circom 与名为WABT的外部 C++ 库静态链接,该库是 WebAssembly 二进制工具包,其工作是将基于文本的 WAT 格式转换为二进制 WASM 格式。Rust WASM 编译器后端不支持链接以不同语言编写的模块,但 WABT 项目已使用 Emscripten 单独编译为 WASM。为了解决这个问题,我们首先剥离了 Circom 调用 WABT 的所有代码并创建了一个包装脚本,当被要求将 Circom 编译为 WASM 时,它会要求启动一个无 WABT 的 Circom 并要求一个 WAT 文件并立即提供它进入另一个 WABT 模块以生成生成的 WASM。

后来,Blaine Bublitz 遇到了WAST库,这是 WASM 二进制格式的纯 Rust 实现,并向 Circom 提交了一组上游更改,以用这个纯 Rust 实现替换 WABT,让我们一劳永逸地摆脱它讨厌的黑客。

WebAssembly 是一个非常轻量级的规范,它本质上代表纯计算——它没有文件、时间甚至输出控制台的概念。这就是WASI的用武之地——它定义了一个标准环境,WASM 程序可以与之交互,以便读取文件、接受输入并打印到输出控制台。但是,WASM 是一项新技术,WASI 更是如此。

此时,我们的 Circom/WASM 实现表面上运行良好:它正在编译我们的代码并生成有效的 WASM 见证生成器。然而,当需要使用 SnarkJS 进行端到端测试时,我们发现所需的 R1CS 文件比我们预期的要大几个字节。经过一番调查,罪魁祸首被确定:在某些情况下,写入文件的特定顺序,跳回先前位置,并继续写入该文件,会在 WASI 运行时的某些实现中引发错误那会把东西写到错误的地方。我们最终通过出售和修补 WASI 文件系统运行时版本来解决这个问题。

至此,我们已经将 Circom 2.0 的工作版本编译为可以在浏览器中运行的 WebAssembly。但除此之外,我们将它打包为一个NodeJS 模块npx circom2,因此任何人都可以通过在他们的计算机上运行来使用 Circom 进行开发。此外,Blaine 还提供了一个程序化 API,可以与hardhat-circom等构建工具集成。

在标准 Circom 语言之上,我们还涉足了一些超出核心语言的扩展。我们添加了直接从外部 URL 导入代码的功能,以及一种在文件底部直接将输入指定到见证生成程序的方式作为注释。

将来,通过支持 PLONK 中的自定义门来进一步扩展语言将会很有趣。

打印

蛇是一种微妙的生物;留下一根悬空的线,您可能已经引入了一个严重的错误,可能会将您的整个协议烧毁。由于涉及的赌注,环境不仅打印输出,而且将其与代码交错以使开发人员能够真正理解代码表面的每一寸,这一点变得越来越重要。

在 zkREPL 中,您只需将鼠标悬停在源代码中的信号上,就会看到一个工具提示,其中显示了从您的见证文件中提取的这些信号的值——看,ma!没有打印声明!

随着时间的推移,用于使人们了解正在运行的代码性质的工具会变得更加先进。例如,我们可能在某个时候能够集成诸如 Franklyn 的Ecne之类的工具的一些静态分析功能,以证明电路实现见证的唯一性。

未来,我们还可以添加新模式,以框图形式可视化和探索电路,并能够放大单个块并查看底层线路,正如 Steven Hao 所建议的那样。

环形

就像比尔博·巴金斯在霍比特人的旅程一样,REPL 的故事以“来来回回”开始和结束。通过回到起点,开发人员可以迭代他们的实现、添加特性、改进算法和修复错误。自 1950 年代以来,计算机已经发展了很多,编程不再需要像以前那样孤独地冥想。随着互联网的出现,循环的过程不再是同步的,而是并行的。

在 Circom 中实施ECDSARSA等工作基本上是协作工作,因此 zkREPL 努力通过简化电路共享和协作来支持这一点。因此,您可以简单地点击“Ctrl-S”并将您的电路副本保存为 Github Gist,然后从浏览器栏中复制 URL 以与朋友分享,或将其作为 iframe 嵌入到教程页面中。

将来,我们可以通过更好地与 Git 集成,或者通过实时多人游戏查看和编辑代码的能力来进一步缩短循环。

结论

由于 0xPARC 从 Xerox PARC 的丰富神话中汲取灵感,这个项目的命名是为了向那个巧妙的小技巧致敬,它将编程从精英仪式转变为无处不在的民主仪式。

整个生态系统还处于起步阶段,像 zkREPL 这样的工具还很原始。如果您有兴趣提供帮助,无论是通过实施上述任何想法,还是通过贡献您自己的愿景,来我们的 Github 回购。

致谢

感谢 gubsheep、Albert Ni 和 0xPARC 学习小组 #1 的其他成员启发并随后测试和使用了这个项目。感谢 Jordi Baylina 和iden3为 Circom 语言和为其提供动力的 SnarkJS 生态系统。感谢 Blaine Bublitz 对 Circom 的 WASM 端口的贡献。感谢 Joel Gustafson、Lily Jordan、Steven Hao、Uma Roy 和 Guillermo Webster 就项目设计进行的有益对话。还要感谢 Alex Klarfeld 在 2017 年首次向我介绍 zk-SNARKs 的概念。