翻译-我从Halo2电路开发中学到的一些小技巧
角色
flowchart LR
证明者-->|输入/输出/证明|验证者
💁♂️证明者:提供 输入 / 输出 / 证明
👩💼 验证者:我不重新运行计算,但我确信输出计算正确
- 准备(开发)阶段
- 👨💻开发者:定义常量
- 👨💻开发者:发送密钥给 💁♂️证明者 及 👩💼验证者
- 验证阶段
- 💁♂️证明者:填写计算过程
- 💁♂️证明者:生成证明
- 👩💼验证者:验证证明
痛苦之处-电路开发和普通开发的不同
- 👩💻普通开发:使用变量、分支、循环、函数来设计逻辑
- 👨💻电路开发:使用表格,描述单元格之间的相互关系来定义约束
- 算术化
- 计算过程
游戏规则
有限域算术中的运算
- 小于p的证书
- 例如p=3
- 则 2 + 2 = 1 (mod 3)
- p通常是一个非常大的数,例如254bits
使用表格布局运算过程
- 单元格:可以填写字段元素
- 列:额外的列会增加 额外的证明/验证花费
- 行:想用多少用多少,一般限制在2^18行以内。
列的类型
- Advice: 引导(输入)列,用于💁♂️证明者,在证明阶段填写隐私数据,仅💁♂️证明者可见。
- Fixed:常量列,👨💻开发者在开发阶段设置的固定值(常量),👩💼验证者可见
- Instance:实例(输出)列,用于💁♂️证明者,在证明阶段填写的公开数据,👩💼验证者可见
- 开发阶段
- 👨💻开发者设置Fixed固定列
- 证明阶段
- 💁♂️证明者填写计算过程
- 填写 Advice列
- 填写 Instance列
- 生成证明
- 验证阶段
- 验证Fixed列、Instance列(结果)和证明
约束:单元格中的那些值是有效的?
自定义门约束:在多项式中定义约束
![[Pasted image 20231110184701.png]]
- 在门约束中定义的表达式中包含的Selctor选择器是一个Fixed列,例如0或1,来在该行禁用或启用
该门约束。 - 在门约束中,我们可以引用前面或后面的单元格,甚至20行以后的单元格,但是带来kzg opening的消耗。
一切都是多项式。
自定义门约束示例-斐波那契数列
示例代码:fibonacci.rs
可以使用4列
也可以使用3列
复制约束 / 相等检查
- 复制约束绑定两个单元格,以便其分配的值必须是相同。
- 最便宜的约束。尽可能多地使 。
- 单元格位置在准备阶段已确定好,在证明解读不可更改。
规则:证明者可能是邪恶的
技巧
限制选项
限制单元格值的选项
- 想要:单元格的值只能是 1,2, 3
- 门表达式:
(x-1)*(x-2)*(x-3) = 0
转化If-else
def foo(a: int, b: int, happy: bool):
if happy:
return a + b
else:
return a * b
门表达式:`happy * (a+b) + (1-happy) * (a*b) - output = 0`
转化For循环
def foo(n: int):
r = 0
for _ in range(n):
r += 5
return r
计算过程
- 我们没有无限的行来存放n,因此我们必须限制n的最大可能值,例如5
- 复制单元格
r0 == counst0
: 已知的r初始值放到常量Fixed列里r5 === out1
: int/out列第一个为函数(验证者)输入,第二个为函数(验证者)输出,即最大行结果和输出值相等。
尝试添加一个证明者可见的Selector选择列
s:选择器列(Advice列)-仅证明者可见
r:步骤结果(Advice列)-仅证明者可见
in/out:输入/输出(Instance列)-验证者可见,仅使用两行
- 门约束表达式:
s * (r_next -r - 5) * (1-s)(r_next - r) = 0
即:
- if选择器为1时:r下一行结果 = r当前行+5
- else选择其为0时:下一行结果 = 当前行结果
但是这种有漏洞-如果selector=0的行不连续,例如:
状态转换技巧
stateDiagram-v2
direction LR
[*] --> Add
[*] --> Pad
Add --> Add
Add --> Pad
Pad --> Pad
Pad --> [*]
Add: 运算+5
Pad: 填充最终结果
门约束表达式:
s *(r_next-r-5)*(1-s)(r_next-r)=0
选择器s应是布尔类型
s * (1-s) == 0
如果当前选择器为0,则下一行选择器也必须为0
(1-s) s_next = 0
最后的点睛之笔:添加输入检查
- 输入in连续整数或后续不变:
s (in_next -in -1) + (1-s)(in_next-in)=0
- 下一行结果为当前行+5或连续不变:
s *(r_next-r-5)*(1-s)(r_next-r)=0
- 选择器s应是布尔类型:
s * (1-s) == 0
- 如果当前选择器为0,则下一行选择器也必须为0:
(1-s) s_next = 0
我们为什么要做这一切?
- 在CPU中,我们有一组指令,这些指令需要可变的时间,在每个周期中,我们运行一条指令。
- 当我们有一个if/else时,执行只遵循一条路径
- 在算术电路中,我们没有循环!我们必须“展平”所有可能的执行路径,并同时“运行”它们,并通过乘以 0 来丢弃我们不需要的路径。
拿走不谢
- 电路开发人员面临的挑战
- 使用“计算跟踪”而不是执行:需要展平路径
- 使用字段元素算术而不是位和字节:需要处理数学和方程式。
- 使用验证思维:需要防止证明者作弊
- 技巧
- if-else 约束布尔值和 约束
s*(true path) +(1-s)*(false path)
- 复杂循环:识别状态转换和设计约束。
- if-else 约束布尔值和 约束