2 个版本
0.4.1 | 2024年1月18日 |
---|---|
0.4.0 | 2023年12月4日 |
#467 在 开发工具 中
每月305 次下载
在 11 个 库中使用 (直接使用 6 个)
400KB
7K SLoC
halo2-base
halo2-base
为使用 halo2
API 编写电路提供了一个嵌入式领域特定语言 (eDSL)。它简化了电路编程,只需在单个建议和选择列上声明约束即可,并提供了内置的电路调优和多线程证明生成支持。
有关更多信息,请参阅 Rust 文档。
虚拟区域管理器
halo2-base
运行的核心框架是 虚拟单元格管理。我们在虚拟区域(在低级原始 halo2 Circuit::synthesize
之外)进行证明生成,并在最后将其映射到 halo2 的 Plonkish 算术的 "原始/物理" 区域。
我们将此形式化为一个新的特质 VirtualRegionManager
。任何 VirtualRegionManager
都与一些列的子集相关联(更一般地,一个物理 Halo2 区域)。它可以以任何方式管理自己的虚拟区域,但必须提供一种确定性的方法将虚拟区域映射到物理区域。
以下是我们的一些虚拟区域管理器示例
SinglePhaseCoreManager
:这与管理我们简单的BasicGateConfig
相关,该配置是一个垂直自定义门,在单个 halo2 挑战阶段。它管理一个包含许多虚拟列(这些是Context
)的虚拟区域。可以将所有虚拟列视为连接成一个单独的大列。然后,给定物理电路中的目标行数,它将单个虚拟列适当地分割成多个物理列。CopyConstraintManager
:这是一个全局管理器,允许引用来自不同区域的虚拟单元。虚拟单元被称为AssignedValue
。尽管名称(来自历史原因),这些值实际上并没有分配到物理电路中。《AssignedValue》是虚拟单元。相反,它们跟踪一个标签,表明它们属于哪个虚拟区域,以及一些其他标识标签,这些标签大致映射到一个 CPU 线程。当引用并使用虚拟单元时,会执行复制,而CopyConstraintManager
会跟踪等价性。当所有虚拟单元都物理分配后,此管理器将在物理单元上施加等价约束。- 此管理器还跟踪使用的常数,删除它们的重复项,并将所有常数分配到专用固定列。它还施加建议单元与固定单元之间的等价约束。
- **非常重要**的是,所有虚拟区域管理器都引用相同的
CopyConstraintManager
,以确保所有复制约束都得到妥善管理。在Circuit::synthesize
的末尾,必须将CopyConstraintManager
进行原始分配,以确保复制约束实际上已传达给原始 halo2 API。
LookupAnyManager
:对于任何类型的查找参数(无论是固定表还是动态表),我们不想在每个电路列上启用此查找参数,因为启用查找是昂贵的。相反,我们分配特殊的建议列(没有选择器),其中查找参数始终开启。当我们想要查找某些值时,我们会将它们复制到特殊的建议单元中。这也意味着您想要查找的单元的物理位置可以是未结构化的。
虚拟区域还设计为能够与原始 halo2 子电路交互。可能使用由 halo2-lib
管理的虚拟区域以及原始 halo2 子电路的电路的整体架构如下所示
BaseCircuitBuilder
halo2-lib
中的电路构建器是一组虚拟区域管理器,以及相关的原始 halo2 列和自定义门的配置。这些列和门的精确配置可以在生成见证后进行微调。我们尚未将电路构建器的概念编码为一个特质。
在 halo2-lib
中使用的核心电路构建器是 BaseCircuitBuilder
。它与 BaseConfig
相关联,该配置由实例列以及 FlexGateConfig
或 RangeConfig
组成:当不需要涉及位范围检查的功能时(通常用于数字的小于比较),使用 FlexGateConfig
,否则 RangeConfig
由 FlexGateConfig
和用于范围检查的固定查找表组成。
BaseCircuitBuilder
的基本结构如下
let k = 10; // your circuit will have 2^k rows
let witness_gen_only = false; // constraints are ignored if set to true
let mut builder = BaseCircuitBuilder::new(witness_gen_only).use_k(k);
// If you need to use range checks, a good default is to set `lookup_bits` to 1 less than `k`
let lookup_bits = k - 1;
builder.set_lookup_bits(lookup_bits); // this can be skipped if you are not using range checks. The program will panic if `lookup_bits` is not set when you need range checks.
// this is the struct holding basic our eDSL API functions
let gate = GateChip::default();
// if you need RangeChip, construct it with:
let range = builder.range_chip(); // this will panic if `builder` did not set `lookup_bits`
{
// basic usage:
let ctx = builder.main(0); // this is "similar" to spawning a new thread. 0 refers to the halo2 challenge phase
// do your computations
}
// `builder` now contains all information from witness generation and constraints of your circuit
let unusable_rows = 9; // this is usually enough, set to 20 or higher if program panics
// This tunes your circuit to find the optimal configuration
builder.calculate_params(Some(unusable_rows));
// Now you can mock prove or prove your circuit:
// If you have public instances, you must either provide them yourself or extract from `builder.assigned_instances`.
MockProver::run(k as u32, &builder, instances).unwrap().assert_satisfied();
证明模式
witness_gen_only
如果只关心证人的生成而不关心电路约束,则设置为true
,否则设置为false。在模拟证明或true
期间,不应该设置为true
。当此标志为true
时,我们会进行某些优化,这些优化只在不需要考虑约束或选择器时才有效。这应该在创建证明密钥后,在真实证明的上下文中进行。
上下文
Context
包含了执行跟踪(电路及其证人值)的所有信息。Context
代表一个“虚拟列”,在Halo2后端存储未分配的约束信息。将电路信息存储在Context
中,而不是直接分配给Halo2后端,允许预先计算电路参数,并保留底层电路信息,以便将其重新排列为多个列,以便在Halo2后端并行化。
在synthesize()
期间,所有Context
的劝告值被连接成一个单一的“虚拟列”,在break_points
处分割成多个真实的Column
,每个代表“虚拟列”的不同子部分。在电路合成期间,所有单元格都分配给Halo2后端的单个Region
中的AssignedCell
。
对于并行证人生成,为每个并行操作创建多个Context
。并行证人生成后,将这些Context
组合成一个单一的“虚拟列”,如上所述。请注意,虽然证人生成可以是多线程的,但每个Context
中的内容顺序以及Context
本身的顺序必须是确定的。
警告:如果您在不受我们库提供的虚拟区域中创建自己的Context
,您必须确保上下文的type_id: &str
是虚拟区域的全球唯一标识符,与用于标识其他虚拟区域的其它type_id
字符串不同。我们建议您在type_id
中包含您的crate名称作为前缀,或者使用module_path!
来生成前缀。将来我们将引入一个宏,在编译时检查此唯一性。
分配值:
尽管名称如此,AssignedValue
是一个虚拟单元
。它包含实际的证人值以及指向虚拟区域内虚拟单元位置的指针。指针由类型ContextCell
给出。我们仅在非证人生成仅模式下存储指针作为优化。
pub struct AssignedValue<F: ScalarField> {
pub value: Assigned<F>,
pub cell: Option<ContextCell>,
}
分配
Assigned
不是一个ZK或电路相关的类型。Assigned
是一个包装枚举,用于字段元素,它将值存储为分数,并使用Montgomery的技巧进行批处理求逆。执行批处理求逆允许通过单个求逆操作计算所有标记值的逆。
pub enum Assigned<F> {
/// The field element zero.
Zero,
/// A value that does not require inversion to evaluate.
Trivial(F),
/// A value stored as a fraction to enable batch inversion.
Rational(F, F),
}
量子单元
QuantumCell
是一个辅助枚举类型,它抽象了在 halo2-base
中的建议列中赋值的情况。没有 QuantumCell
,将现有或常量值赋给建议列需要手动指定赋值时施加的约束,导致代码膨胀。 QuantumCell
处理这些技术操作,开发者所需做的只是指定在 QuantumCell
中添加值的枚举选项。
pub enum QuantumCell<F: ScalarField> {
Existing(AssignedValue<F>),
Witness(F),
WitnessFraction(Assigned<F>),
Constant(F),
}
QuantumCell 包含以下枚举选项。
-
Existing:将存在于建议列中的值赋给建议列。该值是来自您计算某个先前部分的现有值,以
AssignedValue
的形式存在于建议列中。当您将现有单元格添加到表中时,将在建议列中分配一个新单元格,其值等于现有值。然后将在新单元格和“现有”单元格之间添加一个等式约束,以确保这两个单元格始终相等。 -
Witness:将全新的证人值赋给建议列,例如私有输入。当调用
assign_cell()
时,值被封装在Assigned::Trivial()
中,以标记它排除批量求逆。 -
WitnessFraction:将全新的证人值赋给建议列。
WitnessFraction
存在于优化目的,并接受封装在Assigned::Rational()
中的 Assigned 值,这些值标记为批量求逆(见 Assigned)。 -
Constant:一个“已知”的常量值。这里的“已知”是指电路创建时 Prover 和 Verifier 都已知的。当您分配一个常量值时,电路约束表中存在另一个秘密的固定列,其值在电路创建时已固定。当您分配常量值时,您将此值添加到固定列中,将其作为证人添加到建议列中,然后在对固定列和建议列中相应的单元格施加等式约束。
依赖关系
~4–20MB
~239K SLoC