# 一文读懂 zklogin

By [nanne007](https://paragraph.com/@nanne007) · 2023-09-24

---

[zklogin](https://sui.io/zklogin) 是 sui foundation 最新力作，它能够让用户直接通过 google，facebook，apple id （支持 openid connect 即可）登录来创建并控制 sui 钱包，且隐藏了具体的登录信息（用户名，邮箱等）。登录后，可像正常区块链钱包一样发起交易。

本文力图摒弃各种专业术语和底层细节，保留关键的信息要素，缕清 zklogin 怎么实现上述功能。非水文，请坐稳小板凳。

使用场景
----

我们先描述一个典型的使用场景。后文会依据该场景介绍相关操作所设计的底层技术。

假设 sueet 是一款纯本地的 web 钱包应用，可以通过 [http://localhost:3000](http://localhost:3000) 访问。

*   登录页面是一个 **Sign in with Google** 的大按钮。
    

![](https://storage.googleapis.com/papyrus_images/a003ec47d2cf48af7083c0238bee3673a1c2bd46630245bbf3a88bf7e483c727.png)

*   用户点击 **Sign in with Google** ，浏览器跳转到 google oauth 登录页面。
    

![](https://storage.googleapis.com/papyrus_images/882b40918ea829af1ed5255e36082032688ea8ec3183901b83880bcb7c91a0a0.png)

*   用户用 google 的账户密码登录后，页面跳转回 sueet 钱包的页面 [http://localhost:3000](http://localhost:3000)
    
*   此时，sueet 页面在 loading 10秒后，显示登录成功，展示用户的钱包地址，钱包余额等等，如同登录 metamask 后的界面。
    
*   用户可以在这个界面，发起转账等各种交易。
    

上述使用场景是不是相当丝滑。我们来逐步分析流程中的这几个操作。

登录准备
----

用户进入登录页面后， sueet 调用 zklogin 生成以下信息：

*   临时的密钥对：(`eph_sk`, `eph_pk`)。
    
*   登录后的最长有效时间： `max_epoch`
    
*   一个随机数据：`jwt_randomness`
    

然后，将 `eph_pk`, `max_epoch`, `jwt_randomness` 放在一起哈希，生成 `nonce = H(eph_pk, max_epoch, jwt_randomness)`。

**Sign in with Google** 本质上一个调用 google oauth 接口的链接 ，sueet 将生成的 nonce 信息放进链接中，传递给 google。比如：

    https://accounts.google.com/o/oauth2/v2/auth?client_id=$CLIENT_ID&response_type=id_token&redirect_uri=$REDIRECT_URL&scope=openid&nonce=$NONCE
    

用户点击 **Sign in with Google** 后， 会跳转到 Google 的登录鉴权页面。这一步用户直接和 Google 交互，与 sueet 无关。

获取用户信息
------

当用户成功授权后，google 会跳转到 sueet 页面 [http://localhost:3000](http://localhost:3000) 并将用户的 jwt\_token 数据放在跳转链接中，从而让 sueet 可以拿到该数据。jwt\_token 包含了以下信息，payload 中包含了 `iss`, `aud`, `sub`，以及被原封不动返回来的 `nonce` 。

    {
      header: {
        alg: "RS256",
        kid:  "c3afe7a9bda46bae6ef97e46c95cda48912e5979", // Identifies the JWK that should be used to verify the JWT.
        typ: "JWT"
      },
      payload: {
        iss: "https://accounts.google.com", // OpenID 提供商，这里是 谷歌 
        aud: "25769832374-famecqrhe2gkebt5fvqms2263046lj96.apps.googleusercontent.com", // 谷歌给 sueet 分配的应用标识
        sub: "110463452167303000000", // 用户在谷歌里的唯一标识
        nonce: "hTPpgF7XAKbW37rEUS6pEVZqmoI" // 前述构造的nonce，google 原封不动返回
      },
      signature: "3Thp81rDFrKXr3WrY1MyMnNK8kKoZBX9lg-JwFznR-M" // header+payload 的签名数据
    }
    

生成链上地址
------

sueet 拿到 `jwt_token` 后，本地生成一个随机数 `user_salt` （或者由用户输入决定）。

这个时候，sueet 有了足够的信息，来为用户生成 sui 的链上地址：

`address` = `H(iss, H(user_salt,aud, sub))`

对于一个用户来说，(`iss` , `aud`, `sub`) 是固定的，因此，地址基本上是由 `user_salt` 决定。`user_salt` 不同，生成的地址也不一样。

生成地址证明
------

接着，sueet 将数据 `user_salt`, `jwt_token`, `eph_pk`, `max_epoch`, `jwt_randomness` , `sub` 作为隐私参数传递给外部服务 ZK proving service，让其生成 address proof。（如果不信任外部服务，也可以本地生成 proof，可能耗时较长）。证明系统会证明：

1.  `jwt_token` 的合法性，证明它确实是由 google 签署的。
    
2.  nonce 的生成符合预期的规则。即： `jwt_token.payload.nonce = H(eph_pk, max_epoch, jwt_randomness)`
    
3.  传入 的 sub 确实等于 `jwt_token` 中 的sub。即： `jwt_token.payload.sub = sub`
    
4.  地址的生成符合预期的规则。即：`address = H(jwt_token.payload.iss, H(user_salt, jwt_token.payload.aud, jwt_token.payload.sub))`
    

说人话就是：proof 可以证明，生成的 `address` 确实关联一个 google 用户，而且这个 google 账户关联了一个有效期是 `max_epoch` 的密钥对，这个密钥对的公钥是 `eph_pk` 。

从而，如果一个从 address 发起的交易是用 `eph_pk` 对应的私钥签署的， 就可以证明这个交易确实是这个 address 对应的 google 用户发起的。因为只有这个 google 用户掌握了 `eph_pk` 对应的私钥。

除了生成 proof 之外，prover 还会生成公开信息，以便验证 prove 时使用，比如`eph_pk`, `iss`, `address_seed = H(user_salt, aud, sub)`。另外比较重要的一点是，proof 生成后，sueet 可以将其缓存在本地，有效期内不用重复生成。

用户交易
----

拥有了地址证明，用户可以用 (`eph_sk`, `eph_pk`) 签署交易，并附带上缓存的地址证明，提交给 sui 区块链节点。

节点校验
----

节点收到此类交易，会校验：

*   交易发起地址的生成规则，符合 ：`sender_address` ==`H(iss, address_seed)`
    
*   交易签名的有效性。 `eph_pk`.verify(`tx_signature`, tx\_data)
    
*   交易签名用的公钥确实和 zkproof 里使用的 `eph_pk` 一致。
    
*   最后就是，地址证明的验证。这一步需要用到 OpenID 提供商公开的 JWK，比如 google 的是 [https://www.googleapis.com/oauth2/v3/certs](https://www.googleapis.com/oauth2/v3/certs) 。侧面来讲，sui 的节点需要对支持的 openid 服务商的 JWK 达成共识。（比如说，将这些 JWKs 当作一种 resource，发起一个写入交易，走一遍全局共识即可）
    

这样就校验了：交易是由这个 address 关联的 google 账户关联的 (`eph_sk`, `eph_pk`) 签署发布的！

安全性
---

理论情况下，整套流程是非常安全的。应用是本地应用，`user_salt` 和 `proof` 生成都可以在本地实现。用户要么和应用打交道，要么和 google 服务打交道。

目前尚不清楚地址证明的生成效率如何。如果电脑端或手机端，能够在30秒或者更短的时间内生成证明，可能大多数用户会选择本地生成（等待时间不长），安全系数比较高。否则需要依赖三方的证明服务，将敏感信息传递出去，安全性降低。

实际应用场景下，还要解决 `user_salt` 如何保存 的问题。

*   要么 由用户自行承担，登陆时手动输入，缺点是，用户还是需要记一个密码（虽然可能比较简单），而且用户输入的 `salt` 可能比较单一，反而降低了安全性。
    
*   要么 应用本地随机生成并缓存。缺点是，不能多端登录，`salt` 不一样会导致地址也不一样。
    
*   要么 交由三方服务。缺点是，需要将部分信息传递给三方服务，且三方服务知晓了 `salt` 信息，有泄漏风险。
    

使用 `salt` 也有不少优点。比如，用户在换设备后，依旧可以用同一个账户登录，输入相同的 `salt`（相当于密码），即可恢复账户地址，无需私钥。同时，只要输入不同 salt，就可以生成不同的地址，达到切换账户的效果，从而实现多账户功能。

结语
--

zklogin 实现优雅，在保证隐私的情况下，实现免密钥登录，确实拥有 onboarding billions of users 的实力。

短期内，zklogin 无法被其他区块链复制。因为这套方案依赖区块链支持这种独特的交易，并且能够验证地址证明，目前只有 sui 的区块链节点实现了，这是它的独特优势。

但目前 AA 钱包也做的如火如荼，长期来看，类似 zklogin 的方案一定会出现在不同的区块链生态里。拭目以待吧！

如果这篇文章对你有帮助，欢迎在推特转发评论加关注！

[https://twitter.com/nanne007/status/1705867888375263631](https://twitter.com/nanne007/status/1705867888375263631)

参考链接
----

*   官方的文章介绍 [https://docs.sui.io/build/zk\_login](https://docs.sui.io/build/zk_login)
    
*   zklogin client sdk [https://github.com/MystenLabs/sui/tree/main/sdk/zklogin](https://github.com/MystenLabs/sui/tree/main/sdk/zklogin)
    
*   zklogin 电路实现 [https://github.com/sui-foundation/zklogin-circuit](https://github.com/sui-foundation/zklogin-circuit)
    

以及官方出品的流程图

---

*Originally published on [nanne007](https://paragraph.com/@nanne007/zklogin)*
