前言
前面starknet和cairo都看了一点点,发现不如从cairo底层看起,了解团队是如何设计的这门语言,就像我们看java学jvm,看solidity看evm一样。
一、域元素
在现代 CPU 中,基本数据类型是 64 位整数。在数学上,我们可以将其视为对结果模 2 64 2^{64} 264进行计算。例如, − 17 -17 −17 实际上是 2 64 − 17 2^{64}-17 264−17,的确是这样 − 17 ≡ 2 64 − 17 ( m o d 2 64 ) -17\equiv2^{64}-17(mod \quad 2^{64}) −17≡264−17(mod264),如果我们用 2 63 2^{63} 263乘以2,实际是等于0的。
在Cairo中,基础数据类型是一个范围在 0 ≤ x < p 0 \le x \lt p 0≤x<p, P P P是一个大素数。例如, P P P等于 P = 2 251 + 17 ⋅ 2 192 + 1 P=2^{251}+17\cdot2^{192}+1 P=2251+17⋅2192+1是一个标准的选择,所有的计算都要做模P运算。在某些情况下,它对代码没有影响:如果您正在编写一个对值 1 , 2 , 3...1000 1,2,3...1000 1,2,3...1000求和的循环,不会发生溢出并且计算将产生预期值(要注意到 P P P是一个远大于 2 64 2^{64} 264的数),所以非常大的值可以在一个元素中表示)。在某些情况下,您必须像在常规 CPU 中那样防止溢出。
另一方面,在某些情况下,模 P P P需要更加谨慎:
- 除法——与整数除法的常规 CPU 不同,
x / y
定义为 ⌊ x / y ⌋ \lfloor x/y\rfloor ⌊x/y⌋,(所以7 / 2 = 3
) 它可能满足也可能不满足等式(x / y) * y == x
,在Cairo,x / y
的结果被定义为始终满足等式(x / y) * y == x
。如果 y 除以 x j结果为整数,您将在Cairo得到预期的结果(例如 6 / 2 确实会得到 3),但是当 y 不整除 x 时,你可能会得到一个令人惊讶的结果:例如,因为 2 ⋅ P + 1 2 = P + 1 ≡ 1 ( m o d P ) 2\cdot{\frac{P+1}2}={P+1}\equiv1(mod \quad P) 2⋅2P+1=P+1≡1(modP),1 / 2
在Cairo的等于 P + 1 2 \frac{P+1}2 2P+1(而不是 0 或 0.5)。 - 检查一个值是否为偶数:在常规 CPU 中,如果你取一个值 x 并将其乘以 2,结果总是偶数。这在Cairo不成立,正如我们之前看到的,如果我们取
P
+
1
2
\frac{P+1}2
2P+1
乘以 2 得到 1,这是一个奇数。事实上,这个属性在 CPU 中也偶尔出现。例如:x乘以3并不总能得到能被3整除的数(尝试运行以下 C++ 代码:std::cout << 12297829382473034411U * 3 << std::endl;)。)
非确定性计算
由于 Cairo 程序的目标是证明某些计算是正确的,因此我们有时可以走捷径。例如,我们的目标是证明平方根,例如,我们的目标是证明 x =961 的平方根 y 在100范围内。直接的方法是编写一个复杂的代码,从 961 开始,计算它的根并验证这个根是否在要求的范围内。但我们可以做一些更简单的事情,只需证明如果我们从 31 开始并对其进行平方,我们将得到 961(并验证 31 在范围内)。这意味着我们可以从解决方案 (31) 开始,而不是从输入 (961) 开始。我们将这种方法称为非确定性计算。
这里记一个笔记,这里是一个验证与直接计算的区别,就是说在我们书写电路的时候我们可以选择去书写一个直接计算多项式解的电路,但是很明显,对于一个NP问题,求解是复杂的,但是反过来我们可以去写一个验证解的电路,而对于NP问题,我们验证一个解的正确性是容易的(这个感觉就像我们平常喜欢做乘法不喜欢做除法一样)
伪代码如下:
- 猜一个数 y y y(这是不确定的部分)
- 计算 y 2 y^2 y2并确保结果等于 x x x
- 验证 y y y在范围内
Hints
这里是Cairo支持非确定性计算的一个原因,从后面拿到前面来看了。
Cairo 支持非确定性指令。例如,要计算 25 的根,可以简单地写成:
[ap] = 25, ap++;
[ap - 1] = [ap] * [ap], ap++;
虽然这对验证者来说很好,但证明者不能隐式处理这样的表达式(例如,值可以是 5 或 -5),我们必须指示它如何计算根。这是通过添加我们所谓的“hints”来完成的。提示是一段 Python 代码,将在下一条指令之前由证明者执行。
格式如下:
[ap] = 25, ap++;
%{
import math
memory[ap] = int(math.sqrt(memory[ap - 1]))
%}
[ap - 1] = [ap] * [ap], ap++;
要在提示中访问 [ap],我们使用语法 memory[ap](请注意,虽然 [ap] 是有效的 Cairo 表达式,但它不是 Python 中的有效表达式)。此外,如果我们想在提示中访问 Cairo 常量 x,我们可以使用表达式 ids.x。可以用相同的方式访问函数参数和引用。
请注意,提示附加到下一条指令,并在每次执行相应指令之前执行。提示本身不是指令。因此:
%{ print("Hello world!") %}
jmp rel 0; // This instruction is an infinite loop.
将无限多次打印 Hello world(而不是打印一次然后开始无限循环)。
笔记 所以这个hint实际上是在电路以外计算出一个正确的解,然后将解带入到电路去验证正确性
原文给了两个练习,欢迎评论区讨论
内存模型
Cairo 支持只读的非确定性内存,这意味着每个内存单元的值由证明者选择,但它不能随时间改变(在 Cairo 程序执行期间)。我们使用语法 [x] 来表示地址 x 处的内存值。上面的意思是,例如,如果我们在程序开始时断言 [0] = 7,那么在整个运行过程中 [0] 的值将是 7。这里就是前面一直提到了内存不可变
将内存视为一次写入内存通常很方便:您可以将一个值写入单元格一次,但之后不能更改它,因此,我们可以将断言 [0] == 7 的指令解释为“从地址 0 的存储单元中读取值并验证你得到了 7”或者将值 7 写入该存储单元”,具体取决于上下文(在只读的非确定性内存模型中,它们的意思是一样的)。
寄存器
唯一可能随时间变化的值保存在指定的寄存器中:
- ap(分配指针)——指向一个尚未使用的内存单元。
- fp(帧指针)——指向当前函数的帧。所有函数的参数和局部变量的地址都是相对于这个寄存器的值的。当一个函数开始时,它等于 ap。但与ap不同的是,fp的值在整个函数范围内保持不变。
- pc(程序计数器)——指向当前指令。
基本说明
一条简单的 Cairo 指令采用相等断言的形式:
[ap] = [ap - 1] * [fp], ap++;
指出两个存储单元([ap - 1]
和 [fp]
)的乘积必须与下一个未使用的单元([ap]
)的值相同。我们认为这是将两个值的乘积“写入”[ap](实际不可变
)后缀 ap++ 告诉 Cairo 在执行指令后将 ap 加一(要以 ap++ 以外的任何方式更改 ap,您必须使用指定的指令 ap += …)ap++ 本身不是一条指令 - 它是出现在逗号之前的指令的一部分。逗号语法是ap++特有的,不能用来分隔两条指令。
以下列表演示了我们在 Cairo 中的有效断言相等指令:
[fp - 1] = [ap - 2] + [fp + 4];
[ap - 1] = [fp + 10] * [ap], ap++;
[ap - 1] = [fp + 10] + 12345, ap++; // See (a) below.//在1那里解释
[fp + 2] = [ap + 5];
[fp + 2] = 12345;
[ap + 2] = [[ap + 5]]; // See (b) below. //在2那里解释
[ap] = [fp - 3] - [ap + 4]; // See (c) below. //在3那里解释
[ap] = [fp - 3] / [ap + 4]; // See (c) below. //在3那里解释
- 指令中可能出现两种类型的整数:
(1)立即数,既可以作为给定操作中的第二个操作数(例如[ap - 1] = [fp + 10] + 12345
; 中的 12345),也可以作为赋值的独立值(例如[fp + 2] = 12345
; )
(2)偏移量,出现在括号内(例如[ap + 5]
中的5
或[fp - 3]
中的-3
)。
立即数可以是任何field element,而偏移量仅限于范围 [ − 2 15 , 2 15 ] [-2^{15},2^{15}] [−215,215],偏移是针对指针来说的,是指这个指针上下可以指向的内存数量,立即数是个域上的元素
- 指令
[ap + 2] = [[ap + 5]]
;是一个双重解引用指令,您可以在其中获取值[ap + 5]
并将其视为内存地址。 - 这些指令是语法糖——它们被替换为 [fp - 3] = [ap] + [ap + 4]; [fp - 3] = [ap] * [ap + 4];分别。
这里没看懂,就是给减法换成加法,除法换成乘法了?就是说cairo只做加法和乘法么。。可能实现除法消耗gas太高吧
笔记 读到这我认为在cairo中断言相等是一个很重要的概念。。不知道怎么描述
练习
有个练习,欢迎评论区讨论
连续内存
Cairo有一个技术要求,程序访问的内存地址必须是连续的。比如访问地址7和9,那么在程序结束前也必须访问8(访问顺序无关紧要)。如果地址范围中存在小间隙,证明者将自动用任意值填充这些地址。通常,存在这样的间隙是低效的,因为这意味着内存在未被使用的情况下被消耗。引入太多的空隙可能会使证明的生成对于诚实的证明者来说过于昂贵而无法执行。然而,这仍然没有违反可靠性保证——无论如何都不会产生错误的证明。
练习
欢迎评论区讨论所有的练习题。
原文连接 https://www.cairo-lang.org/docs/how_cairo_works/cairo_intro.html
hints:https://www.cairo-lang.org/docs/how_cairo_works/cairo_intro.html