Sui的zkLogin审计公开报告 - ZKSECURITY

  • zksecurity
  • 发布于 2023-11-08 12:43
  • 阅读 888

本文是对Sui Foundation的zkLogin应用程序进行安全审计的报告。zkLogin允许用户使用与SSO登录相关的临时公钥来签署交易,而无需管理复杂的密钥。该方案使用零知识证明来隐藏用户的身份,并依赖OAuth 2.0和JWT进行身份验证,其中报告详细分析了zkLogin的原理、实现,包括RSA的非原生算术以及向量编程在处理偏移量中的应用,同时深入探讨了zkLogin的MPC设置。

Sui 基金会刚刚发布了他们的 zkLogin 应用程序审计公开报告。你可以在此处阅读

正如我们在报告中指出的那样,代码有详尽的文档记录,经过严格的测试,并且规范良好。

以下是报告内容的部分副本。


zkLogin 概述

本节提供 zkLogin 应用程序的简化概述。

zkLogin 是一种在 Sui 中验证用户身份的新方法。它将被集成到协议中,作为与现有身份验证机制相同的机制:Pure Ed25519、ECDSA Secp256k1、ECDSA Secp256r1 和多重签名(有关更多详细信息,请参阅 https://docs.sui.io/learn/cryptography/sui-signatures)。

用密码替换密钥

zkLogin 背后的想法是,用户通常很难管理加密密钥,但习惯于管理密码并进行多因素身份验证流程。因此,将单点登录 (SSO) 桥接到区块链世界,同时保留用户的隐私,可能是改善用户体验同时保持强大安全级别的好方法。

更详细地说,zkLogin 允许用户使用与 SSO 登录相关的临时公钥的签名替换来自公钥(或多个公钥)的交易签名。

SSO 登录被证明为一个签名Token,该Token证明某些用户(很可能是电子邮件地址)已登录到某个已知的 OpenID“身份提供商”。 (在审计时,zkLogin 支持的身份提供商是 Google、Facebook 和 Twitch)。

zkLogin 应用程序使用零知识技术来隐藏用户的真实身份。 例如,用户是 hello@zksecurity.xyz

OAuth 2.0 和 JSON Web Tokens (JWT)

zkLogin 支持的单点登录协议是 OAuth 2.0。用户可以在其中登录到受信任的第三方(Google、Facebook 等),并获得一个签名Token,证明他们以签名 JSON Web Token (JWT) 的形式登录。

签名的 JWT 看起来像三个以点分隔的 base64 编码的有效负载:

eyJhbGciOiJSUzI1NiIsImtpZCI6ImM5YWZkYTM2ODJlYmYwOWViMzA1NWMxYzRiZDM5Yjc1MWZiZhgxOTUiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiI1NzU1MTkyMDQyMzctbXNvcDllcDQ1dTJ1bzk4aGFwcW1uZ3Y4ZDg0cWRjOGsuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiI1NzU1MTkyMDQyMzctbXNvcDllcDQ1dTJ1bzk4aGFwcW1uZ3Y4ZDg0cWRjOGsuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMTA0NjM0NTIxNjczMDM1OTgzODMiLCJlbWFpbCI6IndhbmdxaWFveWkuam95QGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJub25jZSI6IkpNaTZjXzNxWG4xSDhVWDVsYTFQNllEd1Roa041TFp4cWFnVHlqZmlZd1UiLCJpYXQiOjE2ODMzMjMyNjksImV4cCI6MTY4MzMyNjg2OSwianRpIjoiMDEzMzA2YjY1MmY0Zjg1MjUxZTU1OGVhNGFhOWJkYWI3ZmQxNzk3ZiJ9.TCI2XSbEmFc3KVHn2MoZi4OwCM56l59hiSZBdWwMeaCQWCbcJ87OhqtDTOuJMtUclBfkvEDoX_F2VhLJEbUcsFc5XyH_wrPjEqLet3uK1NB8Pqvuk1Q8lMy9nTvSCugGyCRUVhGiOiUfITwq8DhP-NPQ_2vp0NVb_EmHEUxgRniNA-h5JXK2RRxKb1Sx0-yAnerxAamNcvYCOL679Ig9u0N_G_v2cwUVYEm-8XkKpyrUeMv60euxMdO0LDCa1qbOj_l0OmPtMweCMGtVJOCrR3upZ443ttALJ2slsXdXc0Ee9byDoEP9KaPsvMT2ZQX3ZDss_ce3opYDd0snUf-H8A

解码后,第一个有效负载是标头,第二个是 JWT 的内容本身(称为有效负载),第三个是签名。 可以在 jwt.io 上使用调试器来检查此类 JWT:

JWT 示例

有效负载中需要注意的几个重要字段:

  • issuer iss 字段,指示谁颁发/签署了 JWT。
  • audience aud 字段,指示 JWT 的目标对象。
  • subject sub 字段,表示 JWT 正在验证的唯一用户 ID(从颁发者的角度来看)。
  • nonce nonce 字段,反映了应用程序的用户随机数,以防止重放攻击。

验证 JWT

要验证 JWT,需要验证 JWT 上的签名。 要验证 JWT 上的签名,必须知道 JWT 颁发者的公钥。

颁发者都有一个已发布的 JSON Web Key Set (JWKS)。 例如,Facebook 的 JWKS 可以从 https://www.facebook.com/.well-known/oauth/openid/jwks 下载,并且在撰写本文时如下所示:

{
   "keys": [\
      {\
         "kid": "5931701331165f07f550ac5f0194942d54f9c249",\
         "kty": "RSA",\
         "alg": "RS256",\
         "use": "sig",\
         "n": "-GuAIboTsRYNprJQOkdmuKXRx8ARnKXOC9Pajg4KxHHPt3OY8rXRmVeDxTj1-m9TfW6V-wJa_8ncBbbFE-aV-eBi_XeuIToBBvLZp1-UPIjitS8WCDrUhHiJnbvkIZf1B1YBIq_Ua81fzxhtjQ0jDftV2m5aavmJG4_94VG3Md7noQjjUKzxJyUNl4v_joMA6pIRCeeamvfIZorjcR4wVf-wR8NiZjjRbcjKBpc7ztc7Gm778h34RSe9-DLH6uicTROSYNa99pUwhn3XVfAv4hTFpLIcgHYadLZjsHfUvivr76uiYbxDZx6UTkK5jmi51b87u1b6iYmijDIMztzrIQ",\
         "e": "AQAB"\
      },\
      {\
         "kid": "a378585d826a933cc207ce31cad63c019a53095c",\
         "kty": "RSA",\
         "alg": "RS256",\
         "use": "sig",\
         "n": "1aLDAmRq-QeOr1b8WbtpmD5D4CpE5S0YrNklM5BrRjuZ6FTG8AhqvyUUnAb7Dd1gCZgARbuk2yHOOca78JWX2ocAId9R4OV2PUoIYljDZ5gQJBaL6liMpolQjlqovmd7IpF8XZWudWU6Rfhoh-j6dd-8IHeJjBKMYij0CuA6HZ1L98vBW1ehEdnBZPfTe28H57hySzucnC1q1340h2E2cpCfLZ-vNoYQ4Qe-CZKpUAKOoOlC4tWCt2rLcsV_vXvmNlLv_UYGbJEFKS-I1tEwtlD71bvn9WWluE7L4pWlIolgzNyIz4yxe7G7V4jlvSSwsu1ZtIQzt5AtTC--5HEAyQ",\
         "e": "AQAB"\
      }\
   ]
}

如你所见,JWKS 包含几个 Json Web Keys (JWK),由其密钥 ID kid 标识。 通常会显示多个密钥,以支持密钥轮换(保留旧密钥,以便人们有时间轮换其 JWT)。

因为此信息在 JWT 外部,所以网络必须知道颁发者是谁,特别是用于颁发 JWT 的密钥 ID kid 是什么。

请注意,由于 JWT 的颁发者包含在有效负载中,而不是在标头中,因此 zkLogin 电路必须提取此值并在其公共输入中证明它。

The zkLogin 电路

最后,我们可以从高层次上讨论 zkLogin 电路实现的目标。

给定以下公共输入:

  • 我们期望 JWT 具有的 issuer iss 字段(用户需要明确地传递该信息)
  • 颁发者的 RSA 公钥(目前仅支持 RSA 签名算法,这似乎是 OAuth 2.0 中最广泛使用的配置)

它提取以下内容作为公共输出:

  • JWT 的 nonce 字段中包含的临时公钥,以及一些到期信息
  • “地址种子”,稍后在清除后用于导出用户的地址(请参阅稍后如何计算此地址种子)
  • JWT 的标头(网络需要验证,并且还包含颁发者使用的密钥 ID)
  • JWT 的 audience aud 字段

除了提取前面讨论的输出外,电路还执行以下检查:

  1. 它将实际的 JWT 作为私有证据插入到电路中。
  2. 它检查作为公共输入传递的颁发者实际上是 JWT 中包含的颁发者。
  3. 它使用 SHA-256 对 JWT 进行哈希处理,然后使用颁发者的公钥(作为公共输入传递)验证通过获得的摘要的签名(作为私有输入传递)。
  4. 它使用 Poseidon 哈希函数和用户标识符(例如电子邮件)以及一些用户随机性来确定性地派生地址种子。

请注意,签名是在电路内验证的,以避免颁发者能够通过签名和摘要在链上跟踪用户。

此时的想法是让网络确保,除了零知识证明的有效性之外,该地址还与用户密切相关。 为此,验证者基于电路输出的 地址种子 以及 JWT 的颁发者和受众字段,确定性地派生用户的地址。 此信息应该足以唯一地识别用户。

zkLogin 中 RSA 的非原生算术

zkLogin 的 RSA 签名验证的电路实现必须使用大数执行模运算。 具体来说,实现了一种模乘法以支持 2048 位的较大模数。 本节回顾了该实现使用的算法。

问题陈述

电路的目标是计算 a⋅bmodp,但在不同的 本原域 中(其中 本原域 是本原域)。

在非负整数中,我们知道存在 q 和 r<p 使得:

ab=pq+r

当 p<native 时,我们必须确保 pq+r 不会以 native 为模溢出。 但在我们的例子中,我们有 p>native,所以我们必须将模 p 的任何元素分成 k 个 n 位 (并在此表示中强制执行),以便它们可以适应我们的电路。

计算乘法

我们可以将 a 的 k 个 n 位 解释为以下多项式:

a(x)=a0+a1⋅x+a2⋅x2+⋯+ak−1xk−1

b(x) 也是如此。 请注意,值 a 是使用基数计算出的多项式:a=a(2n)(b 也是如此)。

请注意,多项式 ab(x)=a(x)⋅b(x) 表示“未简化的”乘法 a⋅b,其中系数()可以达到 2n+ϵ 位(而不是 n 位,因为 xk−1 的系数看起来像 ak−1b0+ak−2b1+⋯+a1bk−2+a0bk−1)。

另请注意,此多项式的度数为 2(k−1)(这一点稍后会很重要)。

计算模约简

要使用非 本原域 进行模约简,我们还需要证明 r 和 q 也是 n 位的 k 个

然后,我们来看多项式 pqr(x)=p(x)⋅q(x)+r(x),其中(记住)p 是固定为非 本原域。 另请注意,此多项式的度数也是 2(k−1)。

现在我们需要证明这个 pqr(x) 多项式等价于多项式 ab(x)。 我们可以通过显示它们在足够的点上匹配来做到这一点。 也就是说,t(x)=ab(x)−pqr(x) 在足够的点上为 0。 (请注意,如果我们有权访问随机性,我们可以使用 Schwartz-Zippel 引理 来测试此等式与单个随机评估。)

足够的点是 d+1 个点,其中 d 是 pqr(x) 和 ab(x) 的最大度数(正如我们上面提到的,它是 2(k−1))。 所以我们需要 2(k−1)+1=2k−1 个评估点。 该实现对如此多的 x 执行此操作,范围为 [[0,2k−2]]:

// create enough evaluations for pqr(x) // 为 pqr(x) 创建足够的评估
signal v_pq_r[2*k-1];
for (var x = 0; x &lt; 2*k-1; x++) {
    var v_p = poly_eval(k, p, x);
    var v_q = poly_eval(k, q, x);
    var v_r = poly_eval(k, r, x);
    v_pq_r[x] &lt;== v_p * v_q + v_r;
}

// create enough evaluations for t(x) = ab(x) - pqr(x) // 为 t(x) = ab(x) - pqr(x) 创建足够的评估
signal v_t[2*k-1];
for (var x = 0; x &lt; 2*k-1; x++) {
    v_t[x] &lt;== v_ab[x] - v_pq_r[x];
}

检查 t 是否表示零

此时,电路根据 t 的值内插多项式 t。

但是,编码值 a⋅b (ab(x) 的系数)和 pq+r (pqr(x) 的系数)的 不一定相同(因为它们的系数可能会溢出)。

此外,我们不一定有 abi>pqri。 因此,减法可能会下溢。 以下显示,只要 bigint 表示形式为 0 (即 ∑iti⋅2n⋅i=0),这并不重要

首先,如果 ab(x) 和 pqr(x) 确实是相同的多项式(至少是多或少溢出),那么我们应该让第一个肢在第一个 n 位上一致:

t0=ab0−pqr0=0+2n(cab,i−cpqr,i)=2nct,i

现在我们已经证明下一个进位是 ct,i=cab,i−cpqr,i (两个肢的溢出)用于基本情况。 仍然需要证明对于 i 来说它是正确的才能进行归纳。 让我们假设 ct,i−1=cab,i−1−cpqr,i−1,那么我们有下一个进位也被正确地构造(如果肢在前 64 位上一致):

ti=abi−pqri+ct,i−1=abi−pqri+cab,i−1−cpqr,i−1=(abi+cab,i−1)−(pqri−cpqr,i−1)=(0+2ncab,i)−(0+2ncpqr,i)=0+2n(cab,i−cpqr,i)

归纳法如下,最后,如果最终进位为零,那么我们应该让 t(2n)=0。

只要我们证明在整数中为真的东西在我们电路域中也为真,那么前面的方程应该没有健全性问题。

为此,我们想要强制执行对于每个肢 ti=0+2nci,我们都有 ci 被正确地绑定,以免在我们的电路域中环绕。 也就是说,我们知道对于某些 l:

ci∈[−l,l]⟺l+ci∈[0,2l]

因此,如果 l 计算正确,那么它们对执行的进位的位约束就足够了。

zkLogin 中用于偏移量的向量编程

处理偏移量很棘手,并且即使在传统的代码库中也是一个常见的错误来源。 但 zk 电路编程确实类似于电路:任意跳转效率低下(无论如何,使用 circom 对抗 Groth16 后端)。 这鼓励了使用向量编程思维方式进行工作。

向量编程是一种范例,其中操作应用于整个数组或向量,而不是逐个元素。 这种编程风格大量借鉴了线性代数的原理,旨在提供优化和可并行化的实现。 这些优化传统上是被利用的,因为它们对于数据并行问题很有效,在数据并行问题中,必须对数据集中的所有元素执行相同的操作。 但是,在这种范例中工作最终也可以节省大部分时间的约束,最终导致更高效的 zk 电路。

下面我们展示了两个使用向量化来处理解析代码中的边缘情况和偏移量的示例,以说明偏移量出现的位置以及 zkLogin 如何处理这些边缘情况。

向量偏移量示例 1:在 Base64 编码的字符串中搜索 ASCII 子字符串

在整个 zkLogin 算法中,必须从 JWT 中提供的 base64 编码的字符串中提取信息。 虽然这种提取在 zkSNARK 电路之外相当简单,但 zkLogin 代码库中在 zk 电路中使用的机制可以分解为检查 ASCII 子字符串是否存在于 Base64 编码的字符串中,然后将此类字符串切成块。 在 base64 中 ASCII 子字符串的是否存在非常有趣,并在此处高亮显示以供参考。

要遵循说明,template ASCIISubstrExistsInB64circuits/strings.circom:269 中定义。

在我们深入了解 circom 组件的具体工作之前,让我们首先更详细地定义 ASCII 和 Base64 编码。 请注意,为了进行此分析,我们正在考虑 URL 安全的 base64 变体。

ASCII 编码。 ASCII(美国信息交换标准代码)编码的核心是一种旨在将 128 个特定字符(包括控制字符和可打印字符)映射到 7 位整数的方案。 字符范围从“a”到“z”、“A”到“Z”、“0”到“9”,以及各种标点符号、控制字符和其他字符,例如空格。 在计算上下文中,ASCII 字符集通常扩展到 8 位,允许 256 个字符。 这不是严格的 ASCII,而是一个扩展的 ASCII 集,提供其他字符,例如各种语言中使用的重音字母。

映射的 ASCII 表。 ASCII 表提供了从 7 位整数到 ASCII 字符集中的字符的关键映射。 该表仅限于 128 个唯一映射,因为 ASCII 使用 7 位来表示每个字符。

十进制 字符 二进制 十进制 字符 二进制
32 ' ' 0100000 65 ‘A’ 1000001
33 ‘!’ 0100001 66 ‘B’ 1000010
34 ‘"’ 0100010 67 ‘C’ 1000011
35 ‘#’ 0100011 68 ‘D’ 1000100
36 ‘$’ 0100100 69 ‘E’ 1000101
37 ‘%’ 0100101 70 ‘F’ 1000110
38 ‘&’ 0100110 71 ‘G’ 1000111
39 ’’’ 0100111 72 ‘H’ 1001000
40 ‘(’ 0101000 73 ‘I’ 1001001
41 ‘)’ 0101001 74 ‘J’ 1001010
42 ‘*’ 0101010 75 ‘K’ 1001011
43 ‘+’ 0101011 76 ‘L’ 1001100
44 ‘,’ 0101100 77 ‘M’ 1001101
45 ‘-’ 0101101 78 ‘N’ 1001110
46 ‘.’ 0101110 79 ‘O’ 1001111

…以此类推,直到 127。

如上所述,我们可以将 ASCII 字符视为原始 8 位字节,为了简单起见,我们将在本算法中这样做。

Base64 编码。 本质上,base64 编码是一种将二进制数据(通常是任意大小)映射到一组特定的 64 个字符(A-Za-z0-9-_)的机制。

映射的 Base64 表。 base64 编码的核心是查找表。 它将 6 位整数映射到 base64 字母表中的字符。 正如人们可能预料的那样,用于此目的的字符集被限制为 64 个。

十进制 字符 十进制 字符 十进制 字符 十进制 字符
0 A 16 Q 32 g 48 w
1 B 17 R 33 h 49 x
2 C 18 S 34 i 50 y
3 D 19 T 35 j 51 z
4 E 20 U 36 k 52 0
5 F 21 V 37 l 53 1
6 G 22 W 38 m 54 2
7 H 23 X 39 n 55 3
8 I 24 Y 40 o 56 4
9 J 25 Z 41 p 57 5
10 K 26 a 42 q 58 6
11 L 27 b 43 r 59 7
12 M 28 c 44 s 60 8
13 N 29 d 45 t 61 9
14 O 30 e 46 u 62 -
15 P 31 f 47 v 63 _

Base64 语义。 base64 编码操作可以分解为几个逻辑步骤:

  1. 首先,来自二进制数据的字节被分组为三个字节(或 24 位)的块。
  2. 每个 24 位块被划分为四个 6 位整数。
  3. 每个 6 位整数都映射到其对应的 base64 字母字符。 映射由上表控制。
  4. 最后,将结果字符连接起来以形成输出 base64 编码的字符串。

请注意,编码的 base64 字符串的长度(L)应约为原始二进制数据的长度的 (43),由于填充,四舍五入到最接近的 4 的倍数。

算法。 从高层次上讲,此 circom 组件中使用的算法是:

  1. 将 base64 数据的相关块分解为位。
  2. 将 ASCII 探针 分解为位。
  3. 验证这些位是否匹配,注意处理偏移量,有关更多详细信息,请参见下文。

ASCII 位解码很简单。

Base64 位解码,委托给 circuits/base64.circom:37 中的 template DecodeBase64URLChar,在证明者中计算 base64 字符,然后将结果约束到字符的不同连续部分(A-Za-z0-9-_)。

偏移量。 由于 base64 编码的字符映射到 6 位值并紧密地压缩在一起,但 ASCII 值映射到 8 位值。 数据可能无法完美对齐,因此必须进行偏移。

随着更多数据被打包在一起,每个可能性之间会循环出现 3 种可能性。

6 位组 8 位组 偏移量
2n n 4
3n 2n 2
4n 3n 0

同时处理所有偏移量。 最终,ASCIISubstrExistsInB64 计算数据的位偏移量(确保它是有效情况 0 位、2 位、4 位之一),然后对潜在的约束进行编码,以验证这些位是否在 所有偏移量 处匹配,并使用正确的位偏移量作为掩码,以仅有选择地创建实际约束(使用 AssertEqualIfEnabled)。

结论。 我们已经说明了 base64 解码的一般工作方式,以及 zkLogin 代码如何使用向量编程来处理出现的位对齐偏移量。 此外,我们认为该实现是正确的,因为它处理了所有可能的偏移量。

向量偏移量示例 2:使用组进行有效切片

在 zkLogin 电路中,可以重用相同的电路组件 Slice 组件来查询 SHA2 填充的一部分,从 JWT 中删除标头,并多次从 JWT 中提取 声明字符串(无论是在 base64 上下文中还是不在 base64 上下文中)。

基本切片。 切片操作很简单。 circuits/strings.circom:58 中提供了一个 template Slice 的简单直接实现,它提取从较大数组中的索引开始的可能长度可变的子数组。 circom 组件还围绕索引和长度进行了一些边界检查。

切片的直接实现具有的成本主要由 n*m 项控制,其中 n=subarray_lengthm=bigarray_length

分组切片。 除了专门针对 bigarray 开头的切片(zkLogin 在 template SliceFromStart 完成此操作,这也便宜得多),还可以加速常规切片,适用于更小的子数组和中等大小的更大数组。

template SliceGroup 中,电路首先将较大数组和子数组的元素打包在一起(在实践中,它将一个字节数组打包为 16 个字节到每个字段元素中)。

然后,它会计算由于打包而调整后的起始索引和结束索引。 当分组是 2 的幂时,可以使用有效的位移来完成此操作,因为调整只是一个位移。 然后将这些元素取消分组。

最后,处理将要更详细描述的偏移量,请参见下文。

假设分组为 16,则该实现的成本取而代之的是由 18m + (n*m)/32 项控制,实际上要便宜得多!

偏移量。 假设我们一次对 16 个元素进行分组,在这种情况下,有 16 个可能的偏移量要处理,包括 0-15。 在这里,我们再次 同时创建所有解决方案! 具体来说,我们生成一个元素二维数组,其中一个维度是一个偏移量,另一个维度是由偏移量移位的结果。

通过检查将起始索引除以组中元素数量后的余数,可以使用标准 circom 多路复用器组件选择这种情况下的正确偏移量。

结论。 我们讨论了一般切片以及组切片效率的变化。 组切片是 zkLogin 执行的安全优化,并且没有未处理的偏移量。 向量化确保在电路中有效地执行操作。

zkLogin 仪式

由于 zkLogin 构建在 Groth16 证明系统之上,因此它依赖于一组参数,这些参数是作为可信设置的一部分生成的。 为了确保以安全的方式执行可信设置,最佳做法是使用多方计算来分散该过程。

zkLogin 遵循这种模式,通过重用著名的 Perpetual Powers of Tau 仪式。 Perpetual Powers of Tau 仪式是一种线性多方计算,实例化了 MMORPG 论文,参与者可以不断加入协议(一个接一个)以添加他们自己的随机性。 只要有一个参与者诚实地遵循协议(即通过事后销毁他们自己的随机性),该协议就是安全的。

永恒的 tau 仪式力量

利用这些公共参数的常用方法是(可选)加入贡献链(所谓的第 1 阶段),然后派生它以专门用于特定电路(在第 2 阶段中)。 这是因为 Groth16 不是一种通用证明系统,因此 Perpertual Powers of Tau 提供的公共参数不能按原样使用。

有关 Sui 基金会将如何使用 Perpetual Powers of Tau 参数以及如何进行自己的第 2 阶段仪式的更多信息,请参见此帖子:https://twitter.com/SuiNetwork/status/1699948888218898518

初始化过程包括两个阶段:第 1 阶段将采用对 perpetual powers-of-tau (pptau) 项目的第 80 次贡献,你可以在此处阅读有关该项目的更多信息:https://github.com/privacy-scaling-explorations/perpetualpowersoftau/blob/master/0080_carter_response/README.md

对于第 2 阶段,将专门为 zkLogin 系统进行多方计算 (MPC)。

为了确保初始化没有偏差,第 1 阶段的输出将使用 drand 随机信标的第 3298000 轮结果进行随机化 ( https://drand.love)。

对于第 2 阶段,仪式结束后两天将进行额外的随机化,以最大程度地减少贡献的偏差。

一旦所有参与者都完成了他们的贡献,便会宣布此随机化的确切时刻(时期)。

提前共享此信息是为了确保 Sui 基金会或任何个人贡献者都不能影响或预测设置中使用的随机值。

仪式结束后,任何人都可以公开验证如何应用 drand 随机信标值来最终确定设置。

phase2-bn254 的序列化

第一阶段的参数配置为适用于最多 228 个约束的电路。下图展示了参数是如何序列化的。

challenge file

较小的电路可以“修剪”参数,因为它们不需要其完整长度。为此,他们可以简单地修剪上图中的每个部分。

kobigurk/phase2-bn254 工具在 https://github.com/MystenLabs/phase2-bn254 中进行了修补,以支持此修剪(这在补丁之前已损坏)。我们审查了该补丁,发现它是正确的。本质上,问题在于代码使用参数的修剪描述(reduced_circuit_power)反序列化了全长参数(对于 original_circuit_power)。修复反序列化的重要行是:

-    let parameters = CeremonyParams::&lt;Bn256>::new(reduced_circuit_power, batch_size);
+    let parameters = CeremonyParams::&lt;Bn256>::new(original_circuit_power, batch_size);

在 snarkjs 中重新导入 response 文件

Snarkjs 允许导出最新状态,以便其他工具(如 kobigurk/phase2-bn254)可以利用它(例如,贡献或修剪参数,如前所述)。

下图总结了典型流程。

snarkjs flow

使用 snarkjs 导出通过删除 header 以及贡献历史来工作。剩下的是哈希和 ceremony 的最新状态(在上图中称为 accumulator)。导出的文件称为“challenge”。

使用 snarkjs 导入的工作方式是获取一个“response”文件,该文件类似于 challenge 文件,但附加了最新的贡献(见下图)。由于 snarkjs 会跟踪贡献历史记录,因此最新的 snarkjs 状态(在其上执行了上次导出)也用于恢复该历史记录。

response file

Mysten Lab 的补丁修复的问题是,snarkjs 在重新导入时验证预置哈希的一致性,这并不总是与外部工具很好地互操作。

具体来说,kobigurk/phase2-bn254 将预置哈希计算为 HASH(challenge),当多次调用时,它会成为基于 snarkjs 未知的 challenge 的摘要。

Mysten Lab 的修复是删除此检查。我们发现这是一个可以接受的修复,因为该检查是为了完整性检查,并且一个良好协调的 ceremony 不应受到此更改的影响。

  • 原文链接: blog.zksecurity.xyz/post...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
zksecurity
zksecurity
Security audits, development, and research for ZKP, FHE, and MPC applications, and more generally advanced cryptography.