本文介绍了Lambdaworks库及其核心模块,重点介绍了有限域的设计与使用。Lambdaworks是一个用于创建证明系统的库,包含数学、密码学和证明器等模块,其中数学模块的核心是有限域,通过Field和FieldElement的抽象定义,实现了高效的有限域运算,并支持扩展域的应用,以优化配对计算和小字段的证明系统。
在本系列博客文章中,我们将了解 Lambdaworks 是如何实现的,以及开发证明器所需的标准工具。第一部分将简要概述该库,然后重点介绍有限域的设计和使用。
Lambdaworks 的核心是一个用于创建证明系统的库,以及一组随时可用的相关证明器和验证器。在本博客文章中,我们将探讨证明系统的构建块和 Lambdaworks 库。
该库中最相关的部分是:
Provers 拥有一系列证明系统。Crypto 包含一些原语,如 MSM、哈希和 Merkle 树。Math 具有与有限域和椭圆曲线相关的逻辑。
Math 库的核心是有限域,它是我们在 Lambdaworks 中使用的所有结构的主要构建块。
基本结构是在 Field 和它的 FieldElement 之间的关系下设计的。 让我们看看它是如何工作的。
Field 是一个抽象的定义。它知道模数并定义如何执行运算。
我们通常通过实例化一个优化的后端来创建一个新的 Field。例如,这是 Pallas 域的定义:
// 4 是表示该域所需的 64 位 limb 的数量
type PallasMontgomeryBackendPrimeField<T> = MontgomeryBackendPrimeField<T, 4>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MontgomeryConfigPallas255PrimeField;
impl IsModulus<U256> for MontgomeryConfigPallas255PrimeField {
const MODULUS: U256 = U256::from_hex_unchecked(
"40000000000000000000000000000000224698fc094cf91b992d30ed00000001",
);
}
pub type Pallas255PrimeField =
PallasMontgomeryBackendPrimeField<MontgomeryConfigPallas255PrimeField>;
可以看出,定义它的模数并在 PallasMontgomeryBackendPrimeField 上实例化它就足够了。
在内部,它解析所有需要的常量并为该域创建所有需要的操作。请注意,没有涉及宏。 这适用于所有 Lambdaworks 代码。
泛型和 traits 是用于实现泛型的唯一工具。这使得编译器更容易建议可能要调用的函数,并使代码更容易理解。此外,使用最少的 traits 使代码更容易理解。
回到域,你会注意到其他后端对于某些域可能更有效。例如,Mersenne31 和 Goldilocks 是在其后端上定义的。
回到用法,假设我们要创建一个 FieldElement。这就像在 Field 上实例化 FieldElement 并调用一个 from_hex 函数一样简单。
例如:
let an_element = FieldElement::<Stark252PrimeField>::from_hex_unchecked("030e480bed5fe53fa909cc0f8c4d99b8f9f2c016be4c41e13a4848797979c662")
请注意,我们可以将 FieldElement 别名为类似的东西
type FE = FieldElement::<Stark252PrimeField>;
如果我们想缩短代码并且不关心是否明确域。
一旦我们有了一个域,我们就可以进行所有的运算。我们通常建议使用引用,但复制也可以。
let field_a = FE::from_hex("3").unwrap();
let field_b = FE::from_hex("7").unwrap();
// 我们可以使用指针来避免在内部复制值
let operation_result = &field_a * &field_b
// 但是指针和值的所有组合都有效
let operation_result = field_a * field_b
有时,优化操作是首选。例如,
// 我们可以通过将两个数字相乘来求平方
let squared = field_a * field_a;
// 使用指数运算
let squared =
field_a.pow(FE::from_hex("2").unwrap())
// 或者使用优化的函数
let squared = field_a.square()
都计算一个数的平方,但就性能而言,差异很大。
还为常见的常量提供了 一些有用的实例化方法,并且只要可以调用 const 函数。 这是在创建不依赖于 IsField trait 的函数时,因为 Rust 尚不支持 traits 中的 const 函数,
// 为所有域元素定义
// 高效,但对于编译器来说不是 const
let zero = FE::zero()
let one = FE::one()
// 提供了函数的 Const 替代方案,
// 但需要在编译时知道后端。
// 这需要在函数中添加一个 where 子句
let zero = F::ZERO
let one = F::ONE
let const_intstantiated = FE::from_hex_unchecked("A1B2C3");
对于许多用例,我们可以将这些域视为 PrimeField,而不是将它们视为 Field。如果单词 FieldExtension 是不相关的,PrimeField 是正确的选择。
你会注意到 traits 后面跟着一个 Is,因此你可以 使用 IsPrimeField 而不是 接受 IsField 形式的东西,并访问更多函数。最相关的是 .representative()。此函数返回元素作为数字(而非域)的规范表示。
如果内部数字是 Montgomery 形式,则此函数将反转它。
这允许我们进行有意义的比较。 由于域的工作方式类似于元素的循环列表,因此顺序没有多大意义。
例如,如果我们在 F3F3 中,44 可能看起来比 22 大,但 44 也是 11,而 11 似乎小于 22。"哪个元素更大" 的问题没有多大意义。如果我们将一些数字解释为负数,就像其他库一样,这会变得更加混乱。
因此,只有当我们通过 representative() 函数将 FieldElement 解释为数字时,才允许进行比较。
对于序列化,我们建议使用带有 bincode 的 Serde。在保持良好可用性的同时,这已经给出了最好的结果。 默认情况下,序列化以尽可能紧凑的模式完成,并且不可人工读取。
要启用可人工读取的序列化,其中字段写成字符串,可以启用功能 lambdaworks-serde-string。
Serde 在库的所有级别都可用。因此,如果你有一个 FieldElements 结构,你可以简单地推导出序列化。
FieldElements 还有不同的算法来在 ByteConversion trait 中转换为字节。这是一个 from_bytes_le、from_bytes_be、to_bytes_le 和 to_bytes_be。
这些较小的字节转换在执行诸如将数据附加到 transcript 之类的小任务时很有帮助,但是当你必须序列化复杂的结构时,可能会变得很麻烦。
字段扩展用于两种需要稍微不同属性的场景:配对计算和使用带有证明系统的小字段。
在进行配对时,通常使用 1212 度的扩展。此扩展通常使用扩展塔创建,其中我们进行 22 度的扩展、22 度的扩展和 33 度的扩展。这是一种非简单的生成 1212 度扩展的方法。
例如,我们可以在代码中看到:
pub type Degree12ExtensionField = QuadraticExtensionField<Degree6ExtensionField, LevelThreeResidue>;
pub type Degree6ExtensionField = CubicExtensionField<Degree2ExtensionField, LevelTwoResidue>;
使用二次和三次扩展,我们正在构建字段塔。 此工作的关键设计在于 IsField 的内部结构。字段在内部有一个 BaseType,实际上,它可以是一个 BigInteger(我们称之为 UnsignedInteger 以启用 BigInt 的多个后端),也可以是另一个 FieldElement。
这对于此方案来说效果很好,但还有另一个要处理。
当使用使用小字段的证明系统时,例如带有 32 位 BabyBear 的 Stark,我们需要一个扩展来避免安全性遭到破坏。这是因为我们需要从一个比所涉及多项式的次数大得多的集合中采样一个随机的 challenge。 在这种情况下,我们还将使用扩展从更大的集合中采样随机 challenge。
但是这一次,关键问题是我们将在该字段及其子字段之间进行大量运算。这些操作可以比简单地执行它们更有效地解决。想想看,在需要时将一个复数乘以一个实数,而不是不断地乘以复数,即使虚部为 0。
我们将每个 Field 定义为另一个 Field 的 SubField 以解决此问题。 对于未扩展的字段,我们将其定义为其自身的子字段,这是一个你不会注意到的真实陈述。当使用扩展时,定义了两个操作集--一个用于字段,另一个用于字段与其子字段的对抗。
使用哪种操作的解析是通过类型系统完成的,因此在使用该库时,这些优化是不可见的。当使用运算符时,Lambdaworks 会自行选择正确的操作。
有限域是许多证明系统的核心,并且拥有优化的后端对于性能是必要的。Lambdaworks 开发了自己的后端,强调性能和可用性。该库还具有其他功能,例如密码学原语和不同的证明系统。在以后的博客文章中,我们将介绍这些部分,展示如何使用它们,并解释一些设计决策及其可能提供的优势。
- 原文链接: blog.lambdaclass.com/lam...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!