#circuit #lookup-tables #virtual #halo2 #column #context #constraints

nightly halo2-base

为使用 halo2 API 编写电路而设计的嵌入式领域特定语言 (eDSL)。它简化了电路编程,只需在单个建议和选择列上声明约束即可,并提供了内置的电路调优和多线程证明生成支持。

2 个版本

0.4.1 2024年1月18日
0.4.0 2023年12月4日

#467开发工具

Download history 27/week @ 2024-03-13 24/week @ 2024-03-20 43/week @ 2024-03-27 51/week @ 2024-04-03 1/week @ 2024-04-10 18/week @ 2024-04-17 33/week @ 2024-04-24 21/week @ 2024-05-01 41/week @ 2024-05-08 67/week @ 2024-05-15 66/week @ 2024-05-22 57/week @ 2024-05-29 62/week @ 2024-06-05 78/week @ 2024-06-12 97/week @ 2024-06-19 65/week @ 2024-06-26

每月305 次下载
11 库中使用 (直接使用 6 个)

MIT 许可证

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 子电路的电路的整体架构如下所示

Virtual regions with raw sub-circuit

BaseCircuitBuilder

halo2-lib 中的电路构建器是一组虚拟区域管理器,以及相关的原始 halo2 列和自定义门的配置。这些列和门的精确配置可以在生成见证后进行微调。我们尚未将电路构建器的概念编码为一个特质。

halo2-lib 中使用的核心电路构建器是 BaseCircuitBuilder。它与 BaseConfig 相关联,该配置由实例列以及 FlexGateConfigRangeConfig 组成:当不需要涉及位范围检查的功能时(通常用于数字的小于比较),使用 FlexGateConfig,否则 RangeConfigFlexGateConfig 和用于范围检查的固定查找表组成。

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