# ZkLogin 整合
[來源網址](https://docs.sui.io/sui-stack/zklogin-integration/)
以下是錢包或前端應用程式必須實作的 **ZkLogin 交易支援** 高層流程:
1. 錢包建立一個暫時性金鑰對(ephemeral key pair)。
2. 錢包提示使用者完成 OAuth 登入流程,其 nonce 對應到暫時性公鑰。
3. 收到 JSON Web Token(JWT)後,錢包獲取零知識證明。
4. 錢包根據 JWT 獲取唯一的使用者鹽值(user salt)。使用 OAuth 主體識別碼(subject identifier)和鹽值來計算 zkLogin 的 Sui 地址。
5. 錢包使用暫時性私鑰簽署交易。
6. 錢包將交易連同暫時性簽章和零知識證明一併提交。\
以下章節涵蓋具體的實作細節。
## 安裝 zkLogin TypeScript SDK
要在你的專案中使用 zkLogin TypeScript SDK,在專案根目錄執行以下指令:
* npm
* Yarn
* pnpm
```sh
$ npm install @mysten/sui
```
```sh
$ yarn add @mysten/sui
```
```sh
$ pnpm add @mysten/sui
```
如果你想要使用最新的實驗性版本:
* npm
* Yarn
* pnpm
```sh
$ npm install @mysten/sui@experimental
```
```sh
$ yarn add @mysten/sui@experimental
```
```sh
$ pnpm add @mysten/sui@experimental
```
## 獲取 JWT
1. 產生一個暫時性金鑰對。遵循與傳統錢包中產生金鑰對相同的流程。詳情請參見 [Sui SDK](https://sdk.mystenlabs.com/typescript/cryptography/keypairs)。
2. 設定暫時性金鑰對的過期時間。錢包決定最大 Epoch 是當前 Epoch 或更晚的 Epoch。錢包也可決定是否讓使用者自行調整。
3. 使用已設定的 client ID、redirect URL、暫時性公鑰和 nonce 來組裝 OAuth URL:這是應用程式傳送給使用者以便使用計算出的 nonce 完成登入流程的 URL。
```typescript
import { generateNonce, generateRandomness } from '@mysten/sui/zklogin';
import { SuiGrpcClient } from '@mysten/sui/grpc';
const FULLNODE_URL = 'https://fullnode.testnet.sui.io:443'; // or mainnet: https://fullnode.mainnet.sui.io:443
const suiClient = new SuiGrpcClient({ baseUrl: FULLNODE_URL, network: 'testnet' });
const { epoch, epochDurationMs, epochStartTimestampMs } = await suiClient.core.getLatestSuiSystemState();
const maxEpoch = Number(epoch) + 2; // this means the ephemeral key will be active for 2 epochs from now.
const ephemeralKeyPair = new Ed25519Keypair();
const randomness = generateRandomness();
const nonce = generateNonce(ephemeralKeyPair.getPublicKey(), maxEpoch, randomness);
```
Auth flow URL 可以使用 `$CLIENT_ID`、`$REDIRECT_URL` 和 `$NONCE` 來建構。
對於某些提供者(Auth Flow Only 為「是」),JWT 可以在 auth flow 完成後立即在 redirect URL 中找到。
對於其他提供者(Auth Flow Only 為「否」),auth flow 僅會在 redirect URL 中返回一個代碼(`$AUTH_CODE`)。要獲取 JWT,需要使用 token exchange URL 進行額外的 POST 呼叫。
| **提供者** | **Auth Flow URL** | **Token Exchange URL** | **Auth Flow Only** |
| ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ------------------ |
| 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` | N/A | 是 |
| Facebook | `https://www.facebook.com/v17.0/dialog/oauth?client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URL&scope=openid&nonce=$NONCE&response_type=id_token` | N/A | 是 |
| Twitch | `https://id.twitch.tv/oauth2/authorize?client_id=$CLIENT_ID&force_verify=true&lang=en&login_type=login&redirect_uri=$REDIRECT_URL&response_type=id_token&scope=openid&nonce=$NONCE` | N/A | 是 |
| Kakao | `https://kauth.kakao.com/oauth/authorize?response_type=code&client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URL&nonce=$NONCE` | `https://kauth.kakao.com/oauth/token?grant_type=authorization_code&client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URL&code=$AUTH_CODE` | 否 |
| Apple | `https://appleid.apple.com/auth/authorize?client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URL&scope=email&response_mode=form_post&response_type=code%20id_token&nonce=$NONCE` | N/A | 是 |
| Slack | `https://slack.com/openid/connect/authorize?response_type=code&client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URL&nonce=$NONCE&scope=openid` | `https://slack.com/api/openid.connect.token?code=$AUTH_CODE&client_id=$CLIENT_ID&client_secret=$CLIENT_SECRET` | 是 |
| Microsoft | `https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=$CLIENT_ID&scope=openid&response_type=id_token&nonce=$NONCE&redirect_uri=$REDIRECT_URL` | | 是 |
## 解碼 JWT
成功重新導向後,OpenID 提供者會將 JWT 附加為一個 URL 參數。以下是使用 Google 流程的範例。
```text
http://host/auth?id_token=tokenPartA.tokenPartB.tokenPartC&authuser=0&prompt=none
```
`id_token` 參數即為編碼格式的 JWT。你可以將其貼到 [jwt.io](https://jwt.io) 網站上來驗證編碼 token 的正確性並檢視其結構。
要解碼 JWT,你可以使用像 `jwt_decode` 這樣的函式庫,並將回應映射到提供的型別 `JwtPayload`:
```typescript
const decodedJwt = jwt_decode(encodedJWT) as JwtPayload;
export interface JwtPayload {
iss?: string;
sub?: string; //Subject ID
aud?: string[] | string;
exp?: number;
nbf?: number;
iat?: number;
jti?: string;
}
```
## 使用者鹽值管理
zkLogin 使用使用者鹽值來計算 zkLogin 的 Sui 地址(參見[定義](https://docs.sui.io/sui-stack/zklogin-integration))。鹽值必須是一個 16 位元組的值,或是一個小於 `2n**128n` 的整數。應用程式有以下幾種維護使用者鹽值的選項:
1. 客戶端:
* 選項 1:在存取錢包時要求使用者輸入鹽值,將責任轉移給使用者,使用者必須記住它。
* 選項 2:瀏覽器或行動裝置儲存:確保有適當的工作流程來防止使用者在更換裝置或瀏覽器時失去錢包存取權。一種做法是在新錢包設定時透過電子郵件發送鹽值。
2. 後端服務,提供一個端點,為每個使用者一致地返回唯一的鹽值。
* 選項 3:將使用者識別碼(例如 `sub`)到使用者鹽值的對應儲存在常規資料庫中(例如 `user` 或 `password` 資料表)。鹽值對每個使用者是唯一的。
* 選項 4:實作一個服務,保留一個主種子值,並透過驗證和解析 JWT 後使用金鑰派生來產生使用者鹽值。例如,使用 `HKDF(ikm = seed, salt = iss || aud, info = sub)`(定義於[此處](https://github.com/MystenLabs/fastcrypto/blob/e6161f9279510e89bd9e9089a09edc018b30fbfe/fastcrypto/src/hmac.rs#L121))。此選項不允許更換主種子或更改 client ID(即 `aud`),否則會產生不同的使用者地址,導致資金損失。
以下是由 Mysten Labs 維護的鹽值伺服器(使用選項 4)的請求與回應範例。如果你想要使用 Mysten Labs 的鹽值伺服器,請參閱 [Enoki 文件](https://docs.enoki.mystenlabs.com/)並與我們聯繫。僅接受使用已列入白名單的 client ID 進行驗證的有效 JWT。
```javascript
curl -X POST https://salt.api.mystenlabs.com/get_salt -H 'Content-Type: application/json' -d '{"token": "$JWT_TOKEN"}'
```
```javascript
Response: {"salt":"129390038577185583942388216820280642146"}
```
使用者鹽值用於斷開 OAuth 識別碼(`sub`)與鏈上 Sui 地址之間的連結,避免將 Web2 憑證與 Web3 憑證關聯起來。雖然遺失或誤用鹽值可能使此連結暴露,但不會危及資金控制或 zkLogin 資產權限。
## 取得使用者的 Sui 地址
OAuth 流程完成後,JWT 可以在 redirect URL 中找到。結合使用者鹽值,可以如下方式推導出 zkLogin 地址:
```typescript
import { jwtToAddress } from '@mysten/sui/zklogin';
const zkLoginUserAddress = jwtToAddress(jwt, userSalt, false);
```
## 取得零知識證明
下一步是獲取 ZK 證明。這是對暫時性金鑰對的證明(attestation),證明該暫時性金鑰對是有效的。
首先,產生擴展的暫時性公鑰(extended ephemeral public key),作為 ZKP 的輸入。
```typescript
import { getExtendedEphemeralPublicKey } from '@mysten/sui/zklogin';
const extendedEphemeralPublicKey = getExtendedEphemeralPublicKey(ephemeralKeyPair.getPublicKey());
```
如果先前的暫時性金鑰對已過期或無法存取,你需要獲取新的 ZK 證明。
由於產生 ZK 證明可能在客戶端耗費大量資源且速度較慢,錢包應使用專門用於 ZK 證明產生的後端服務端點。
有兩種選項:
1. 呼叫由 Mysten Labs 維護的證明服務。
2. 在你的後端使用提供的 Docker 映像檔來執行證明服務。
### 呼叫 Mysten Labs 維護的證明服務
如果你想要使用 Mysten 託管的 ZK 證明服務用於主網,請參閱 [Enoki 文件](https://docs.enoki.mystenlabs.com/)並與我們聯繫以獲取存取權限。
你可以使用 BigInt 或 Base64 編碼來表示 `extendedEphemeralPublicKey`、`jwtRandomness` 和 `salt`。以下範例展示了兩個請求,第一個使用 BigInt 編碼,第二個使用 Base64 編碼。
```sh
$ curl -X POST $PROVER_URL -H 'Content-Type: application/json' \
-d '{"jwt":"$JWT_TOKEN", \
"extendedEphemeralPublicKey":"84029355920633174015103288781128426107680789454168570548782290541079926444544", \
"maxEpoch":"10", \
"jwtRandomness":"100681567828351849884072155819400689117", \
"salt":"248191903847969014646285995941615069143", \
"keyClaimName":"sub" \
}'
```
```sh
$ curl -X POST $PROVER_URL -H 'Content-Type: application/json' \
-d '{"jwt":"$JWT_TOKEN", \
"extendedEphemeralPublicKey":"ucbuFjDvPnERRKZI2wa7sihPcnTPvuU//O5QPMGkkgA=", \
"maxEpoch":"10", \
"jwtRandomness":"S76Qi8c/SZlmmotnFMr13Q==", \
"salt":"urgFnwIxJ++Ooswtf0Nn1w==", \
"keyClaimName":"sub" \
}'
```
回應:
```json
{
"proofPoints": {
"a": [
"17267520948013237176538401967633949796808964318007586959472021003187557716854",
"14650660244262428784196747165683760208919070184766586754097510948934669736103",
"1"
],
"b": [
[
"21139310988334827550539224708307701217878230950292201561482099688321320348443",
"10547097602625638823059992458926868829066244356588080322181801706465994418281"
],
[
"12744153306027049365027606189549081708414309055722206371798414155740784907883",
"17883388059920040098415197241200663975335711492591606641576557652282627716838"
],
["1", "0"]
],
"c": [
"14769767061575837119226231519343805418804298487906870764117230269550212315249",
"19108054814174425469923382354535700312637807408963428646825944966509611405530",
"1"
]
},
"issBase64Details": {
"value": "wiaXNzIjoiaHR0cHM6Ly9pZC50d2l0Y2gudHYvb2F1dGgyIiw",
"indexMod4": 2
},
"headerBase64": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEifQ"
}
```
### 如何處理 CORS 錯誤
為避免前端應用程式中可能發生的 CORS 錯誤,建議將此呼叫委派給後端服務。
回應可以映射到 zkLogin SDK 中 `getZkLoginSignature` 的 `inputs` 參數型別。
```typescript
const proofResponse = await post('/your-internal-api/zkp/get', zkpRequestPayload);
export type PartialZkLoginSignature = Omit<
Parameters<typeof getZkLoginSignature>['0']['inputs'],
'addressSeed'
>;
const partialZkLoginSignature = proofResponse as PartialZkLoginSignature;
```
### 在你的後端執行證明服務
1. 在下載 zkey 之前,先安裝 [Git Large File Storage](https://git-lfs.com/)(一個用於大型檔案版本控制的開源 Git 擴充)。
2. 下載 [Groth16 證明金鑰 zkey 檔案](https://docs.circom.io/getting-started/proving-circuits/)。所有 Sui 網路都有可用的 zkey。
3.
* 主 zkey(用於主網和測試網)
```sh
$ wget -O - https://raw.githubusercontent.com/sui-foundation/zklogin-ceremony-contributions/main/download-main-zkey.sh | bash
```
* 測試 zkey(用於開發網)
```sh
$ wget -O - https://raw.githubusercontent.com/sui-foundation/zklogin-ceremony-contributions/main/download-test-zkey.sh | bash
```
* 要驗證下載的檔案包含正確的 zkey 檔案,你可以執行以下指令來檢查 Blake2b 雜湊值:`b2sum ${file_name}.zkey`。
| **網路** | **zkey 檔案名稱** | **雜湊值** |
| -------------- | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| 主網、測試網 | `zkLogin-main.zkey` | `060beb961802568ac9ac7f14de0fbcd55e373e8f5ec7cc32189e26fb65700aa4e36f5604f868022c765e634d14ea1cd58bd4d79cef8f3cf9693510696bcbcbce` |
| 開發網 | `zkLogin-test.zkey` | `686e2f5fd969897b1c034d7654799ee2c3952489814e4eaaf3d7e1bb539841047ae8ee5fdcdaca5f4ddd76abb5a8e8eb77b44b693a2ba9d4be57e94292b26ce2` |
3\. 下一步,你需要來自 [mysten/zklogin 倉庫](https://hub.docker.com/r/mysten/zklogin)的兩個 Docker 映像檔(標記為 `prover` 和 `prover-fe`)。有一個 docker compose 檔案可以自動化此流程。從與 YAML 檔案相同的目錄中,使用已下載的 zkey 來執行 `docker compose`。
```yaml
services:
backend:
image: mysten/zklogin:prover-stable
volumes:
# The ZKEY environment variable must be set to the path of the zkey file.
- ${ZKEY}:/app/binaries/zkLogin.zkey
environment:
- ZKEY=/app/binaries/zkLogin.zkey
- WITNESS_BINARIES=/app/binaries
frontend:
image: mysten/zklogin:prover-fe-stable
command: '8080'
ports:
# The PROVER_PORT environment variable must be set to the desired port.
- '${PROVER_PORT}:8080'
environment:
- PROVER_URI=http://backend:8080/input
- NODE_ENV=production
- DEBUG=zkLogin:info,jwks
# The default timeout is 15 seconds. Uncomment the following line to change it.
# - PROVER_TIMEOUT=30
```
```text
ZKEY=<path_to_zkLogin.zkey> PROVER_PORT=<PROVER_PORT> docker compose up
```
1. 要呼叫該服務,支援以下兩個端點:
* `/ping`:測試服務是否已啟動。執行 `curl http://localhost:PROVER_PORT/ping` 應返回 `pong`。
* `/v1`:請求和回應與 Mysten Labs 維護的服務相同。
2. `/ping`:測試服務是否已啟動。執行 `curl http://localhost:PROVER_PORT/ping` 應返回 `pong`。
3. `/v1`:請求和回應與 Mysten Labs 維護的服務相同。\
需注意以下考量:
* 後端服務(mysten/zklogin:prover-stable)計算量很大。請至少使用建議的 16 核心和 16GB 記憶體。使用較弱的實例可能導致逾時錯誤,訊息為 "Call to rapidsnark service took longer than 15s"。你可以調整環境變數 `PROVER_TIMEOUT` 來設定不同的逾時值,例如 `PROVER_TIMEOUT=30` 表示 30 秒的逾時。
* 如果你想要從原始碼編譯 prover(出於效能原因),請參見 [rapidsnark](https://github.com/MystenLabs/rapidsnark#compile-prover-in-server-mode) 的分支。你需要編譯並以伺服器模式啟動 prover。
* 設定 `DEBUG=*` 會開啟 prover-fe 服務中的所有日誌,其中一些可能包含個人身份資訊(PII)。在正式環境中考慮使用 `DEBUG=zkLogin:info,jwks`。
## 組裝 zkLogin 簽章並提交交易
首先,使用先前產生的暫時性私鑰來簽署交易位元組。這與[傳統金鑰對簽署](https://sdk.mystenlabs.com/typescript/cryptography/keypairs)相同。確保交易的 `sender` 也已定義。
```typescript
import { SuiGrpcClient } from '@mysten/sui/grpc';
const ephemeralKeyPair = new Ed25519Keypair();
const client = new SuiGrpcClient({ baseUrl: '<YOUR_RPC_URL>', network: 'mainnet' });
const txb = new Transaction();
txb.setSender(zkLoginUserAddress);
const { bytes, signature: userSignature } = await txb.sign({
client,
signer: ephemeralKeyPair, // This must be the same ephemeral key pair used in the ZKP request
});
```
接下來,透過組合 `userSalt`、`sub`(主體 ID)和 `aud`(受眾)來產生 `addressSeed`。
設定 `addressSeed` 和部分 zkLogin 簽章作為 `inputs` 參數。
你現在可以透過組合 ZK 證明(`inputs`)、`maxEpoch` 和暫時性簽章(`userSignature`)來序列化 zkLogin 簽章。
```typescript
import { genAddressSeed, getZkLoginSignature } from '@mysten/sui/zklogin';
const addressSeed = genAddressSeed(
BigInt(userSalt!),
'sub',
decodedJwt.sub,
decodedJwt.aud,
).toString();
const zkLoginSignature = getZkLoginSignature({
inputs: {
...partialZkLoginSignature,
addressSeed,
},
maxEpoch,
userSignature,
});
```
最後,執行交易。
```typescript
client.executeTransaction({
transaction: bytes,
signatures: [zkLoginSignature],
});
```
## 快取暫時性私鑰和 ZK 證明
如先前所述,每個 ZK 證明都與一個暫時性金鑰對綁定。因此,你可以重複使用該證明來簽署任意數量的交易,直到暫時性金鑰對過期(即當前 Epoch 超過 `maxEpoch`)。
你可能希望將暫時性金鑰對與 ZKP 一起快取,以便日後使用。
然而,暫時性私鑰需要像傳統錢包中的金鑰對一樣被視為機密。這是因為如果暫時性私鑰和 ZK 證明同時洩露給攻擊者,他們通常就可以代表使用者簽署任何交易(使用前面所述的相同流程)。
因此,你不應該將它們持久儲存在任何平台上不安全的儲存位置。例如,在瀏覽器上,使用 session storage 而非 local storage 來儲存暫時性金鑰對和 ZK 證明。這是因為 session storage 會在瀏覽器 session 結束時自動清除其資料,而 local storage 中的資料會無限期保存。
## 效能考量
與傳統簽章相比,zkLogin 簽章需要更長的生成時間。例如,Mysten Labs 維護的 prover 通常需要大約三秒才能返回一個證明,該證明在具有 16 個 vCPU 和 64 GB RAM 的機器上執行。使用更強大的機器,例如具有實體 CPU 或圖形處理單元(GPU)的機器,可以進一步縮短證明時間。
請仔細考慮你的應用程式需要向 prover 發出的請求數量。廣義而言,需要考量的正確指標是活躍使用者會話數(active user sessions),而不是簽章數量。這是因為你可以快取相同的 ZK 證明並在整個會話期間重複使用,如先前所述。例如,如果你預期每天有一百萬個活躍使用者會話,那麼你需要一個能夠處理每秒一到兩個請求(RPS)的 prover,假設流量均勻分佈。
Mysten Labs 維護的 prover 設定為自動擴展以處理流量高峰。如果你不確定 Mysten Labs 能否處理特定數量的請求,或預期你的應用程式需要發出的 prover 請求突然激增,請在 [Discord](https://discord.gg/sui) 上聯繫我們。我們的計劃是水平擴展 prover 以處理你所需的任何 RPS。
---
# 什麼是 zkLogin?
[頁面網址](https://docs.sui.io/sui-stack/zklogin-integration/zklogin)
**zkLogin:** 是一個 Sui 原語,它允許你使用 OAuth 憑證來發送交易。這是一種 Sui 原語,讓你能夠從一個 Sui 地址發送交易,使用 OAuth 憑證而無需公開連結兩者。
zkLogin 的設計目標如下:
* **簡化入門流程:** zkLogin 讓你能夠使用熟悉的 OAuth 登入流程在 Sui 上進行交易,無需處理加密金鑰或記住助記詞。
* **自我託管:** zkLogin 交易需要通過標準 OAuth 登入流程獲得使用者批准。OAuth 提供者無法代表你進行交易。
* **安全性:** zkLogin 是一個雙因素驗證方案。發送交易需要同時具備來自近期 OAuth 登入的憑證,以及一個不歸 OAuth 提供者管理的鹽值。攻擊者即使入侵了 OAuth 帳號,也無法從你的 Sui 地址進行交易,除非他們也同時取得鹽值。
* **隱私性:** 零知識證明防止第三方將 Sui 地址與其對應的 OAuth 識別碼關聯起來。
* **可選的驗證身份:** 你可以選擇驗證用於推導特定 Sui 地址的 OAuth 識別碼。這為可驗證的鏈上身份層奠定了基礎。
* **可及性:** zkLogin 與其他 Sui 原語整合,例如 sponsored transactions 和 multisig。
* **嚴謹性:** zkLogin 的程式碼已經由兩家專精於零知識的審計公司獨立[審計](https://github.com/sui-foundation/security-audits/blob/main/docs/zksecurity_zklogin-circuits.pdf)過。用於創建公共參考字串(CRS)的公開 zkLogin 儀式,包含了來自超過 100 位參與者的貢獻。\
zkLogin 為 Sui 帶來的關鍵差異化因素為:
1. Sui 原生支援:與其他區塊鏈無關的解決方案不同,zkLogin 僅部署於 Sui。這意味著 zkLogin 交易可以無縫地與 multisig 和 sponsored transactions 結合。
2. 無需額外信任的自我託管:Sui 利用 JWT 中的 nonce 欄位來提交暫時性公鑰,因此無需任何受信任方來管理持久性私鑰。JWK 本身是一個由驗證者權益的法定人數(quorum)共同同意的預言機,無需信任任何權威來源。
3. 完全隱私:除了零知識證明和暫時性簽章之外,無需在鏈上提交任何內容。
4. 與現有身份提供者相容:zkLogin 與採用 OpenID Connect 的提供者相容,這意味著除了 OAuth 提供者本身之外,你不需要信任任何中介的身份發行者或驗證者。\
如果你是想要將 zkLogin 整合到你的應用程式或錢包中的開發者,請參見 [zkLogin 整合指南](/sui-stack/zklogin-integration)。
## **OpenID 提供者**
下表列出了可以支援 zkLogin 的 OpenID 提供者,或目前正在審查中以確定是否能支援 zkLogin 的提供者。
| **提供者** | **可以支援?** | **開發網** | **測試網** | **主網** |
| ------------- | -------------- | ---------- | ---------- | -------- |
| Facebook | 是 | 是 | 是 | 是 |
| Google | 是 | 是 | 是 | 是 |
| Twitch | 是 | 是 | 是 | 是 |
| Apple | 是 | 是 | 是 | 是 |
| Slack | 是 | 是 | 否 | 否 |
| Kakao | 是 | 是 | 否 | 否 |
| Microsoft | 是 | 是 | 否 | 否 |
| AWS (Tenant)\*| 是 | 是 | 是 | 是 |
| Karrier One | 是 | 是 | 是 | 是 |
| Credenza3 | 是 | 是 | 是 | 是 |
| RedBull | 審查中 | 否 | 否 | 否 |
| Amazon | 審查中 | 否 | 否 | 否 |
| WeChat | 審查中 | 否 | 否 | 否 |
| Auth0 | 審查中 | 否 | 否 | 否 |
| Okta | 審查中 | 否 | 否 | 否 |
* Sui 支援 AWS (Tenant),但該提供者是按租戶啟用的。請聯繫我們以獲取更多資訊。
## 術語與符號
本節描述了 [OpenID 規範](https://openid.net/specs/openid-connect-core-1_0#Terminology)中定義的相關 OpenID 術語,以及它們在 zkLogin 中的使用方式,以及協議細節的定義。
### OpenID 提供者(OP)
OpenID 提供者是一個能夠對終端使用者進行身份驗證,並向依賴方(Relying Party)提供有關身份驗證事件和終端使用者資訊的 OAuth 2.0 授權伺服器。這在 JWT 酬載中由 `iss` 欄位識別。請查閱可用的 OP 表格,了解 zkLogin 目前支援的實體。
### 依賴方(RP)或客戶端
依賴方是一個需要終端使用者身份驗證以及來自 OpenID 提供者的資訊的 OAuth 2.0 客戶端應用程式。這由 OP 在你建立應用程式時指派。這在 JWT 酬載中由 `aud` 欄位識別,指的是任何支援 zkLogin 的錢包或應用程式。
### 主體識別碼(sub)
主體識別碼是發行者內對終端使用者的局部唯一識別碼,RP 打算使用它。Sui 使用此作為 key claim 來推導使用者地址。
### JSON Web Key(JWK)
JSON Web Key 是一個 JSON 資料結構,代表 OP 的一組公鑰。可以查詢公開端點(如 <https://www.googleapis.com/oauth2/v3/certs>)來獲取與提供者的 `kid` 對應的有效公鑰。當與 JWT 標頭中的 `kid` 匹配時,JWT 可以根據酬載及其對應的 JWK 來驗證。在 Sui 中,所有驗證者獨立呼叫 JWK 端點,並且所有支援的提供者的最新 JWK 視圖會在協議升級期間更新。JWK 的正確性由驗證者權益的法定人數(2f+1)來保證。
### JSON Web Token(JWT)
JSON Web Token 在你完成 OAuth 登入流程後,存在於 RP 的 redirect URI 中(如 `https://redirect.com?id_token=$JWT_TOKEN`)。JWT 包含 `header`、`payload` 和 `signature`。簽章是根據 `jwt_message = header + . + payload` 及其由 `kid` 標識的 JWK 來驗證的 RSA 簽章。`payload` 包含一個名稱-值對的 JSON。
**Header**
| **名稱** | **範例值** | **使用方式** |
| -------- | --------------------------------------- | --------------------------------------------------------- |
| `alg` | RS256 | zkLogin 僅支援 RS256(RSA + SHA-256)。 |
| `kid` | c3afe7a9bda46bae6ef97e46c95cda48912e5979 | 標識應該用於驗證 JWT 的 JWK。 |
| `typ` | JWT | zkLogin 僅支援 JWT。 |
**Payload**
| **名稱** | **範例值** | **使用方式** |
| -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `iss` | <https://accounts.google.com> | 指派給 OAuth 提供者的唯一識別碼。 |
| `aud` | [575519200000-msop9ep45u2uo98hapqmngv8d8000000.apps.googleusercontent.com](http://575519200000-msop9ep45u2uo98hapqmngv8d8000000.apps.googleusercontent.com) | OAuth 提供者指派給依賴方的唯一識別碼。 |
| `nonce` | hTPpgF7XAKbW37rEUS6pEVZqmoI | 由依賴方設定的值。支援 zkLogin 的錢包需要將其設定為暫時性公鑰的雜湊值、過期時間和一個隨機數的雜湊。 |
| `sub` | 110463452167303000000 | 指派給使用者的唯一識別碼。 |
對於 zkLogin 交易,`iat` 和 `exp` 宣告(時間戳)不被使用。取而代之的是,`nonce` 指定了過期時間。
### Key Claim
用於推導你的地址的 key claim,例如 `sub` 或 `email`。自然地,理想情況下是使用固定不變且永遠不會更改的宣告。zkLogin 目前支援 `sub` 作為 key claim,因為 OpenID 規範要求提供者不得更改此識別碼。
### 符號
1. `(eph_sk, eph_pk)`:用於產生暫時性簽章的私鑰和公鑰對。簽署機制與傳統的交易簽署相同,但它是暫時性的,因為它僅在短暫的會話期間儲存,並且可以在新的 OAuth 會話時刷新。暫時性公鑰用於計算 `nonce`。
2. `nonce`:嵌入在 JWT 酬載中的應用程式定義欄位,計算方式為暫時性公鑰、JWT 隨機數和最大 Epoch(Sui 定義的過期 Epoch)的雜湊值。具體而言,一個與 zkLogin 相容的 nonce 必須以如下方式傳入:`nonce = ToBase64URL(Poseidon_BN254([ext_eph_pk_bigint / 2^128, ext_eph_pk_bigint % 2^128, max_epoch, jwt_randomness]).to_bytes()[len - 20..])`,其中 `ext_eph_pk_bigint` 是 `ext_eph_pk` 的 BigInt 表示。
3. `ext_eph_pk`:暫時性公鑰的位元組表示(`flag || eph_pk`)。大小取決於簽章方案的選擇(由 flag 標記,定義於[簽章](/develop/transactions/transaction-auth/auth-overview)中)。
4. `user_salt`:引入的一個值,用於斷開 OAuth 識別碼與鏈上地址之間的連結。
5. `max_epoch`:JWT 過期的 Epoch。這是 Sui 中使用的 `u64`。
6. `kc_name`:key claim 的名稱,例如 `sub`。
7. `kc_value`:key claim 的值,例如 `110463452167303000000`。
8. `hashBytesToField(str, maxLen)`:使用 [Poseidon hash](https://eprint.iacr.org/2019/458.pdf) 將 ASCII 字串雜湊為一個有限域元素。
## 實體
1. 應用程式前端:這描述了支援 zkLogin 的錢包或前端應用程式。前端負責儲存暫時性私鑰,引導你完成 OAuth 登入流程,以及建立和簽署 zkLogin 交易。
2. 鹽值備份服務:這是一個後端服務,負責為每個唯一的使用者返回一個鹽值。關於其他維護鹽值的策略,請參見 [zkLogin 整合指南](/sui-stack/zklogin-integration)。
3. 零知識證明服務:這是一個後端服務,負責根據 JWT、JWT 隨機數、使用者鹽值和最大 Epoch 來產生零知識證明。此證明連同暫時性簽章一起提交到鏈上,以用於 zkLogin 交易。
## zkLogin 如何運作
在高層次上,zkLogin 協議的運作方式如下:
1. JWT 是來自 OAuth 提供者的已簽署酬載,其中包含一個名為 nonce 的使用者定義欄位。zkLogin 透過將 nonce 定義為公鑰和過期 Epoch 來使用 [OpenID Connect OAuth 流程](https://openid.net/developers/how-connect-works/)。
2. 錢包儲存一個暫時性金鑰對,其中暫時性公鑰定義在 nonce 中。暫時性私鑰在短暫的會話期間簽署交易。從 JWT 產生一個 Groth16 零知識證明,該證明隱藏了敏感欄位。
3. 交易連同暫時性簽章和零知識證明一起提交到鏈上。Sui 驗證者在驗證暫時性簽章和證明後執行交易。
4. zkLogin 地址不是根據公鑰來推導的,而是從 `sub`(使用者識別碼)、`iss`(提供者)、`aud`(應用程式)和 `user_salt`(一個斷開 OAuth 識別碼與鏈上地址連結的值)推導出來。

#### 第 0 步:zkLogin 使用 Groth16 來實例化 zkSNARK,這需要一個與電路相關的公共參考字串(CRS)。
一個儀式產生了 CRS,該 CRS 用於在證明服務中產生證明金鑰,以及在 Sui 驗證者中產生驗證金鑰。
#### 第 1-3 步:登入 OpenID 提供者(OP)以獲取包含 nonce 的 JWT。
產生一個暫時性金鑰對 `(eph_sk, eph_pk)`,並將 `eph_pk`、過期時間(`max_epoch`)和隨機數(`jwt_randomness`)嵌入到 nonce 中。登入後,JWT 出現在應用程式的 redirect URL 中。
#### 第 4-5 步:應用程式前端將 JWT 發送到鹽值服務。該服務根據 `iss`、`aud` 和 `sub` 返回唯一的 `user_salt`。
#### 第 6-7 步:將 JWT、使用者鹽值、暫時性公鑰、JWT 隨機數和 key claim 名稱(例如 `sub`)發送到證明服務。
證明服務產生一個零知識證明,該證明:
* 確認 nonce 被正確推導。
* 確認 key claim 值與對應的 JWT 欄位匹配。
* 驗證提供者對 JWT 的 RSA 簽章。
* 確認地址與 key claim 和使用者鹽值一致。

#### 第 8 步:應用程式根據 `iss`、`aud` 和 `sub` 計算你的地址。
#### 第 9-10 步:使用暫時性私鑰簽署交易,並將交易連同暫時性簽章、ZK 證明和其他輸入一起提交到 Sui。
在第 10 步之後,Sui 驗證者根據提供者的 JWK(由共識儲存)和暫時性簽章來驗證 ZK 證明。
## 地址定義
地址是根據以下輸入計算的:
1. 地址標誌(address flag):對於 zkLogin 地址,`zk_login_flag = 0x05`。這用作網域分隔符。
2. `kc_name_F = hashBytesToField(kc_name, maxKCNameLen)`:key claim 的名稱,例如 `sub`。位元組序列使用 `hashBytesToField`(下面定義)映射到 BN254 中的有限域元素。
3. `kc_value_F = hashBytesToField(kc_value, maxKCValueLen)`:key claim 的值,使用 `hashBytesToField` 映射。
4. `aud_F = hashBytesToField(aud, maxAudValueLen)`:依賴方(RP)識別碼。
5. `iss`:OpenID 提供者(OP)識別碼。
6. `user_salt`:引入的一個值,用於斷開 OAuth 識別碼與鏈上地址的連結。\
最終,Sui 推導出 `zk_login_address = Blake2b_256(zk_login_flag, iss_L, iss, addr_seed)`,其中 `addr_seed = Poseidon_BN254(kc_name_F, kc_value_F, aud_F, Poseidon_BN254(user_salt))`。
## 儀式
為了保護 OAuth 工件(artifact)的隱私,會提供一個零知識擁有證明。zkLogin 採用 Groth16 [zkSNARK](https://en.wikipedia.org/wiki/Non-interactive_zero-knowledge_proof) 來實例化零知識證明,因為它在證明大小和驗證效率方面是最有效的通用 zkSNARK。
然而,Groth16 需要由受信任方設定一個針對特定計算的公共參考字串(CRS)。由於 zkLogin 預期要確保高價值交易的安全性和關鍵智能合約的完整性,因此不能將系統的安全性建立在單一實體的誠實之上。因此,為了為 zkLogin 電路產生 CRS,必須執行一個協議,該協議的安全性建立在假設大量參與者中有一小部分是誠實的基礎上。
Sui zkLogin 儀式是一個由多元參與者執行的加密多方計算(MPC),用於產生 CRS。該儀式遵循由 Bowe、Gabizon 和 Miers 描述的 MPC 協議 [MMORPG](https://eprint.iacr.org/2017/1050.pdf)。協議分為兩個階段進行。第一階段產生橢圓曲線元素指數上的一系列秘密量 tau 的冪次。因為此階段與電路無關,該儀式採用了現有社群貢獻的 [perpetual powers of tau](https://github.com/privacy-scaling-explorations/perpetualpowersoftau/tree/master) 的結果。儀式所處的是第二階段,該階段特定於 zkLogin 電路。
MMORPG 協議是一個順序協議,允許無限數量的參與者依序參與,無需任何事先的同步或排序。每個參與者需要下載前一個參與者的輸出,產生自己的熵(entropy),然後將它疊加到收到的結果上,產生自己的貢獻,然後將其轉交給下一位參與者。如果至少有 1 位參與者忠實地遵循協議、產生強熵並可靠地丟棄它,協議就保證安全性。
### 儀式是如何進行的?
邀請發送給了超過 100 位具有多元背景和關係的人士:Sui 驗證者、密碼學家、Web3 專家、世界知名的學者和商業領袖。儀式計劃於 2023 年 9 月 12 日至 18 日舉行,但允許參與者在他們方便的時候加入,沒有固定的時間段。
由於 MPC 是順序進行的,每個貢獻者需要等待前一位貢獻者完成,才能收到前一個貢獻,按照 MPC 步驟操作並產生自己的貢獻。由於這種結構,設有一個佇列供參與者等待,而比他們先加入的人則完成他們的貢獻。為了驗證參與者,每個人都收到了一個唯一的啟動碼(activation code)。啟動碼是一個簽署金鑰對的私鑰,具有雙重目的:它允許協調伺服器將參與者的電子郵件與貢獻關聯起來,並使用對應的公鑰來驗證貢獻。
參與者可以透過瀏覽器或 Docker 進行貢獻。瀏覽器選項對貢獻者來說更為使用者友好,因為一切都在瀏覽器中發生。Docker 選項需要 Docker 設定,但更加透明,因為 Dockerfile 和貢獻者原始碼是開源的,整個過程都是可驗證的。此外,瀏覽器選項使用 [snarkjs](https://github.com/iden3/snarkjs),而 Docker 選項使用 [Kobi's implementation](https://github.com/iseriohn/phase2-bn254)。這提供了軟體多樣性,貢獻者可以選擇他們信任的任何方法進行貢獻。此外,參與者可以透過輸入隨機文字或進行隨機游標移動來產生熵。
zkLogin 電路和儀式客戶端[程式碼](https://github.com/sui-foundation/zk-ceremony-client)已開源,並在儀式前提供給參與者審查。此外,還發布了開發者文件和來自 zkSecurity 的電路[審計報告](https://github.com/sui-foundation/security-audits/blob/main/docs/zksecurity_zklogin-circuits.pdf)。該儀式採用了 [perpetual powers of tau](https://github.com/privacy-scaling-explorations/perpetualpowersoftau/tree/master/0080_carter_response) 中第一階段的 [challenge #0081](https://pse-trusted-setup-ppot.s3.eu-central-1.amazonaws.com/challenge_0081)(來自 80 次社群貢獻的結果),該階段與電路無關。應用了 [Drand](http://drand.love) 隨機信標在 Epoch #3298000 的輸出以消除偏差。對於第二階段,儀式共有 111 次貢獻,其中 82 次來自瀏覽器,29 次來自 Docker。最後,應用了 Drand 隨機信標在 Epoch #3320606 的輸出以消除貢獻中的偏差。所有中間檔案都可以按照[第一階段](https://github.com/sui-foundation/zklogin-ceremony-contributions/blob/main/phase1/README.md)和[第二階段](https://github.com/sui-foundation/zklogin-ceremony-contributions/blob/main/phase2/README.md)的說明重新產生。
最終的 CRS 以及每位參與者的貢獻記錄都在一個公開倉庫中可用。貢獻者會收到他們正在處理的前一個貢獻的雜湊值,以及他們貢獻後的結果雜湊值,顯示在螢幕上並透過電子郵件發送。他們可以將這些雜湊值與儀式網站上公開可用的記錄進行比較。此外,任何人都可以檢查雜湊值是否正確計算,以及每個貢獻是否正確地納入最終的參數中。
最終,最終的 CRS 被用於產生證明金鑰和驗證金鑰。證明金鑰用於為 zkLogin 產生零知識證明,儲存在零知識證明服務中。驗證金鑰作為驗證者軟體的一部分[部署](https://github.com/MystenLabs/sui/pull/13822)([release 1.10.1](https://github.com/MystenLabs/sui/releases/tag/mainnet-v1.10.1) 中的協議版本 25),用於在 Sui 上驗證 zkLogin 交易。
## 安全性與隱私
以下章節逐一說明所有 zkLogin 的工件(artifact)、其安全假設,以及遺失或洩露的後果。
### JWT
JWT 的有效性範圍限定於 client ID(`aud`),以防止釣魚攻擊。證明的同源策略(same origin policy)防止了為惡意應用程式獲取的 JWT 被用於 zkLogin。針對特定 client ID 的 JWT 會透過 redirect URL 直接發送到應用程式前端。洩露的 JWT(針對特定 client ID)可能危及使用者的隱私,因為這些 token 通常包含敏感資訊,如使用者名稱和電子郵件。此外,如果後端鹽值伺服器負責使用者鹽值管理,JWT 可能會被利用來獲取你的鹽值,這會帶來額外的風險。
然而,只要對應的暫時性私鑰是安全的,JWT 洩露並不意味著資金損失。
### 使用者鹽值
使用者鹽值是存取 zkLogin 錢包所必需的。此值對於 ZK 證明產生和 zkLogin 地址推導都是至關重要的。
使用者鹽值的洩露並不意味著資金損失,但它使攻擊者能夠將你的主體識別碼(例如 `sub`)與 Sui 地址關聯起來。根據使用的是成對的(pairwise)還是公開的主體識別碼,這可能會產生問題。特別是,如果使用成對的 ID(例如 Facebook),則沒有問題,因為主體識別碼對每個 RP 都是唯一的。然而,對於公開的可重用 ID(例如 Google 和 Twitch),全域唯一的 `sub` 值可以用於識別使用者。
### 暫時性私鑰
暫時性私鑰的存續期與在 nonce 中指定的最大 Epoch 相關聯,用於創建有效的零知識證明。如果它被遺失,可以產生一個新的暫時性私鑰來進行交易簽署,並伴隨使用新 nonce 產生的全新零知識證明。然而,如果暫時性私鑰被入侵,攻擊者還需要獲取使用者鹽值和有效的零知識證明才能轉移資金。
### 證明
僅獲取證明本身無法創建有效的 zkLogin 交易,因為還需要對交易的暫時性簽章。
### 隱私
預設情況下,OAuth 主體識別碼(例如 `sub`)和 Sui 地址之間沒有連結。這就是使用者鹽值的目的。
JWT 預設不會發布到鏈上。揭示的值包括 `iss`、`aud` 和 `kid`,以便可以計算公開輸入的雜湊值,任何敏感欄位如 `sub` 在產生證明時都被作為私密輸入使用。
ZK 證明服務和鹽值服務(如果已維護)可以關聯使用者身份,因為使用者鹽值和 JWT 是已知的,但這兩個服務的設計是無狀態的(stateless)。
## 常見問題解答
#### zkLogin 與哪些提供者相容?
* zkLogin 可以支援基於 OAuth 2.0 框架、採用 OpenID Connect 的提供者。這是相容 OAuth 2.0 提供者的一個子集。請查看最新的表格以了解所有已啟用的提供者。其他相容的提供者將在未來的協議升級中啟用。
#### zkLogin 錢包與傳統私鑰錢包有何不同?
* 傳統私鑰錢包要求你持續記住助記詞和密碼短語,需要安全儲存以防止私鑰洩露。另一方面,zkLogin 錢包僅需要一個帶有會話過期的暫時性私鑰儲存,以及帶有過期的 OAuth 登入流程。遺忘暫時性金鑰不會導致資金損失,因為你可以隨時重新登入以產生新的暫時性金鑰和新的 ZK 證明。
#### zkLogin 與 MPC 或 multisig 錢包有何不同?
* 多方計算(MPC)和 multisig 錢包依賴於多個金鑰或分配多個金鑰份額,然後定義一個接受簽章的門檻值。zkLogin 不分割任何個別的私鑰,但暫時性私鑰是使用新的 nonce 在你向 OAuth 提供者進行身份驗證時註冊的。zkLogin 的主要優勢在於你不需要在任何地方管理任何持久性私鑰,甚至不需要使用任何私鑰管理技術,如 MPC 或 multisig。你可以將 zkLogin 視為一個地址的雙因素驗證(2FA)方案,其中第一部分是你的 OAuth 帳號,第二部分是你的鹽值。此外,由於 Sui 原生支援 multisig 錢包,你隨時可以將一個或多個 zkLogin 簽署者包含在 multisig 錢包中以增強安全性,例如在 k-of-N 設定中使用 zkLogin 部分作為 2FA。
#### 如果 OAuth 帳號被入侵,zkLogin 地址會怎樣?
* 因為 zkLogin 是一個雙因素驗證系統,入侵了你 OAuth 帳號的攻擊者無法存取你的 zkLogin 地址,除非他們也單獨入侵了你的鹽值。
#### 如果你失去了對 OAuth 帳號的存取權,你會失去對 zkLogin 地址的存取權嗎?
* 是的。你必須能夠登入你的 OAuth 帳號並產生當前的 JWT 才能使用 zkLogin。
#### 失去 OAuth 憑證是否意味著 zkLogin 錢包中的資金損失?
* 遺忘的 OAuth 憑證通常可以透過在該提供者處重設密碼來恢復。在不理想的情況下,如果你的 OAuth 憑證被洩露,對手仍然需要 `user_salt`,但他們也會知道要接管哪個錢包。現代的 `user_salt` 提供者可能有額外的 2FA 安全措施,以防止即使是出示有效且未過期 JWT 的實體也被提供使用者鹽值。同樣重要的是要強調,由於 zkLogin 地址不暴露任何關於使用者身份或錢包的資訊,僅透過監控區塊鏈進行的目標攻擊更加困難。如果你永久失去對 OAuth 帳號的存取權,對該錢包的存取將會遺失。如果希望從遺失的 OAuth 帳號中恢復,對錢包提供者的一個好建議是支援原生 Sui multisig 功能並添加備份方法。甚至可以擁有一個所有簽署者都使用 zkLogin 的 multisig 錢包,例如一個 1-of-2 的 multisig zkLogin 錢包,其中第一部分是 Google OAuth,第二部分是 Facebook OAuth。
#### 你可以將傳統私鑰錢包轉換或合併為 zkLogin 錢包嗎?反之亦然?
* 不可以。zkLogin 錢包地址的推導方式與私鑰地址不同。
#### 我的 zkLogin 地址會改變嗎?
* zkLogin 地址是從 `sub`、`iss`、`aud` 和 `user_salt` 推導出來的。如果你使用相同的 OAuth 提供者登入相同的錢包,地址不會改變,因為 JWT 中的 `sub`、`iss`、`aud` 和 `user_salt` 保持不變,即使 JWT 本身每次登入時看起來可能不同。然而,如果你使用不同的 OAuth 提供者登入,你的地址會改變,因為 `iss` 和 `aud` 對每個提供者的定義不同。此外,每個錢包或應用程式維護自己的 `user_salt`,因此使用相同的提供者為不同的錢包登入也可能導致不同的地址。
#### 你可以使用相同的 OAuth 提供者擁有多個地址嗎?
* 是的,這可以透過為每個帳號使用不同的錢包提供者或不同的 `user_salt` 來實現。這對於在不同帳號之間分離資金非常有用。
#### zkLogin 錢包是託管的嗎?
* zkLogin 錢包是非託管(non-custodial)或非託管(unhosted)錢包。託管或託管錢包是指第三方(託管人)代表錢包使用者控制私鑰。對於 zkLogin 錢包,不存在這樣的第三方。相反,zkLogin 錢包可以被視為一個 2-of-2 的 multisig,其中兩個憑證分別是你的 OAuth 憑證和鹽值。OAuth 提供者、錢包供應商、ZK 證明服務或鹽值服務提供者都不是託管人。
#### 產生零知識證明很昂貴,每個交易都需要產生新的證明嗎?
* 不需要。證明產生僅在暫時性金鑰對過期時才需要。由於 nonce 是由暫時性公鑰(`eph_pk`)和過期時間(`max_epoch`)定義的,零知識證明在 JWT 中 nonce 所提交的過期時間之前都是有效的。零知識證明可以被快取,相同的暫時性金鑰可以用於簽署交易,直到它過期。
#### zkLogin 在行動裝置上可以使用嗎?
* zkLogin 是 Sui 的原生原語,而不是某個特定應用程式或錢包的功能。任何 Sui 開發者都可以使用它,包括在行動裝置上。
#### 如果失去 OAuth 憑證,可以進行帳號恢復嗎?
* 是的,你可以遵循 OAuth 提供者的恢復流程。暫時性私鑰可以刷新,在完成新的 OAuth 登入流程後,你可以獲取新的零知識證明並使用刷新後的金鑰簽署交易。
#### zkLogin 電路有哪些假設?
* 由於 Groth16 的工作方式,Sui 對 JWT 中的多個欄位施加了長度限制。一些受長度限制的欄位包括 `aud`、`iss`、JWT 標頭和酬載。例如,zkLogin 目前只能處理長度最多為 120 的 `aud` 值。總的來說,Sui 努力確保這些限制盡可能寬鬆。Sui 在檢視了盡可能多的可獲取的 JWT 之後決定了這些值。
#### zkLogin 與其他支援社群登入的解決方案有何不同?
* 雖然使用 Web2 憑證為 Web3 錢包提供社群登入不是一個新概念,但現有的解決方案具有以下一個或多個信任假設:
1. 信任一個不同的網路或方來驗證 Web2 憑證,而非區塊鏈本身,通常涉及由受信任方發布到鏈上的 JWK 預言機。
2. 信任某些方來管理持久性私鑰,無論是使用 MPC、閾值密碼學還是安全飛地(secure enclaves)。
3. 依賴智能合約在鏈上驗證 JWT 並揭示隱私欄位,或在鏈上驗證零知識證明,這可能非常昂貴。
* 一些現有的已部署解決方案依賴於這些假設中的部分。[Web3Auth](https://web3auth.io/) 和 [Auth0 social login](https://auth0.com/learn/social-login) 需要將自訂的 OAuth 驗證器部署到 [Web3auth Auth Network](https://github.com/web3auth) 節點來驗證 JWT。[Magic Wallet](https://wallet.magic.link/) 和 [Privy](https://privy.io/) 也需要自訂的 OAuth 身份發行者和驗證器來採用 [DID 標準](https://www.w3.org/TR/did-1.0/)。所有這些解決方案仍然需要持久性私鑰管理,無論是透過委派給受信任方如 AWS、Shamir 秘密分享或 MPC。
#### 如何在鏈下驗證 zkLogin 簽章?
* 以下選項支援使用 Sui 上的 JWK 狀態和當前 Epoch 來對交易資料或個人訊息驗證 zkLogin 簽章。
1. 使用 [Sui TypeScript SDK](https://sdk.mystenlabs.com/typescript)。這會初始化一個 [GraphQL 客戶端](/references/sui-graphql)並在底層呼叫端點。
2. 直接使用 GraphQL 端點:`https://sui-[network].mystenlabs.com/graphql`,將 `[network]` 替換為適當的值。更多詳情請參見 [GraphQL 文件](/references/sui-api/sui-graphql/beta/reference/operations/queries/verify-zk-login-signature)。如果你不打算執行任何伺服器或處理任何 JWK 輪換,建議使用此方式。
3. 使用 [Sui Keytool CLI](/references/cli/keytool)。建議用於除錯用途。
```sh
$ sui keytool zk-login-sig-verify --sig $ZKLOGIN_SIG --bytes $BYTES --intent-scope 3 --network devnet --curr-epoch 3
```
4. 使用自行託管的伺服器端點並呼叫此端點,如 `zklogin-verifier` 中所述。這提供了邏輯的靈活性。
#### 我可以在 multisig 錢包中使用 zkLogin 嗎?
* 是的。更多詳情請參見 [Multisig 指南](https://sdk.mystenlabs.com/typescript/cryptography/multisig#multisig-with-zklogin)。
---
# 設定 OpenID 提供者
[URL](https://docs.sui.io/sui-stack/zklogin-integration/developer-account)
要將 zkLogin 與你的應用程式整合,你需要至少來自一個可用提供者的 OAuth 客戶端。你將在你的 zkLogin 專案中使用來自這些提供者的 Client ID 和 redirect URI。例如,以下 TypeScript 程式碼建構了一個用於測試的 Google 登入 URL。
```typescript
const REDIRECT_URI = '<YOUR_SITE_URL>';
const params = new URLSearchParams({
// Configure client ID and redirect URI with an OpenID provider
client_id: $CLIENT_ID,
redirect_uri: $REDIRECT_URI,
response_type: 'id_token',
scope: 'openid',
// See below for details about generation of the nonce
nonce: nonce,
});
const loginURL = `https://accounts.google.com/o/oauth2/v2/auth?${params}`;
```
## **OpenID 提供者**
下表列出了可以支援 zkLogin 的 OpenID 提供者,或目前正在審查中以確定是否能支援 zkLogin 的提供者。
| **提供者** | **可以支援?** | **開發網** | **測試網** | **主網** |
| ------------- | -------------- | ---------- | ---------- | -------- |
| Facebook | 是 | 是 | 是 | 是 |
| Google | 是 | 是 | 是 | 是 |
| Twitch | 是 | 是 | 是 | 是 |
| Apple | 是 | 是 | 是 | 是 |
| Slack | 是 | 是 | 否 | 否 |
| Kakao | 是 | 是 | 否 | 否 |
| Microsoft | 是 | 是 | 否 | 否 |
| AWS (Tenant)\*| 是 | 是 | 是 | 是 |
| Karrier One | 是 | 是 | 是 | 是 |
| Credenza3 | 是 | 是 | 是 | 是 |
| RedBull | 審查中 | 否 | 否 | 否 |
| Amazon | 審查中 | 否 | 否 | 否 |
| WeChat | 審查中 | 否 | 否 | 否 |
| Auth0 | 審查中 | 否 | 否 | 否 |
| Okta | 審查中 | 否 | 否 | 否 |
* Sui 支援 AWS (Tenant),但該提供者是按租戶啟用的。請聯繫我們以獲取更多資訊。
## 設定 OpenID 提供者
選擇一個標籤頁以取得有關使用相關提供者設定 client ID(前面範例中的 `$CLIENT_ID`)和 redirect URI(前面範例中的 `$REDIRECT_URI`)的說明。
### Google
1. 將瀏覽器導航到 [Google Cloud 儀表板](https://console.cloud.google.com/projectselector2/home/dashboard)。登入或註冊一個 Google Cloud 帳號。
2. 使用 Google Cloud 儀表板導航,打開 **APIs & Services** > **Credentials**。

3. 在 Credentials 頁面上,選擇 **CREATE CREDENTIALS** > **OAuth client ID**。

4. 設定你的應用程式的 **Application type** 和 **Name**。

5. 在 **Authorized redirect URIs** 區段中,點擊 **ADD URI** 按鈕。在欄位中設定你的 redirect URI 值。這應該是錢包或應用程式前端。

6. 點擊 **Create**。如果成功,Google Cloud 會顯示 **OAuth client created** 對話框,其中包含元資料,包括你的 **Client ID**。點擊 **OK** 關閉對話框。
\
你的新 OAuth 客戶端現在應該出現在 Credentials 頁面的 **OAuth 2.0 Client IDs** 區段中。點擊客戶端旁邊出現的 **Client ID** 將其複製到剪貼簿。點擊客戶端名稱以存取 redirect URI 和其他客戶端資料。
### Facebook
1. 註冊一個 Facebook 開發者帳號並存取[儀表板](https://developers.facebook.com/apps/)。
2. 選擇 **Build your app**,然後 **Products**,然後 **Facebook Login**,在那裡你可以找到 client ID。設定 redirect URL。這應該是錢包或應用程式前端。

3. 註冊 Facebook 開發者帳號

4. 前往 Settings
### Twitch
1. 註冊一個 Twitch 開發者帳號。存取[儀表板](https://dev.twitch.tv/console)。
2. 前往 **Register Your Application**,然後 **Application**,在那裡你可以找到 client ID。設定 redirect URL。這應該是錢包或應用程式前端。

3. 註冊 Twitch 開發者帳號
4. 前往 Console,註冊一個 Kakao 開發者帳號。存取[儀表板](https://developers.kakao.com/console/app)並新增一個應用程式。

---
# zkLogin 身份驗證
[URL](https://docs.sui.io/sui-stack/zklogin-integration/zklogin-demo)
此範例示範如何將 [zkLogin](/sui-stack/zklogin-integration/zklogin) 整合到 React 應用程式中,讓使用者可以使用熟悉的 OAuth 提供者(Google、Apple)進行身份驗證,並在無需管理私鑰的情況下與 Sui 區塊鏈互動。
## 何時使用此模式
當你需要以下功能時使用此模式:
* 讓沒有 Sui 錢包的使用者入門,讓他們使用 Google 或 Apple 登入。
* 從 JWT token 和鹽值推導出確定性的 Sui 地址,使使用者始終獲得相同的地址。
* 產生並管理代表已驗證使用者簽署交易的暫時性金鑰對。
* 從 prover 服務獲取零知識證明,並將其作為 zkLogin 簽章附加到交易中。
* 在 React 前端建立完整的 zkLogin 流程,無需任何後端伺服器。
## 你將學到什麼
此範例教導:
* **暫時性金鑰對:** zkLogin 系統使用短期的 Ed25519 金鑰對,這些金鑰對在可設定的 Epoch 數量內有效。金鑰對簽署交易,但 zkLogin 簽章包裝了結果並證明簽署者擁有 OAuth 身份而不揭示它。
* **OAuth 彈出視窗流程:** 應用程式在彈出視窗中打開 OAuth 提供者,從 URL hash 中提取 `id_token`,並透過 `postMessage` 將其發送到父視窗。這在身份驗證期間保持主應用程式狀態完整。
* **ZK 證明產生:** prover 服務接收 JWT、擴展的暫時性公鑰、最大 Epoch、隨機數和鹽值,並返回零知識證明。此證明讓網路可以在不看到 JWT 的情況下驗證交易是由 OAuth 身份持有者授權的。
* **地址推導:** `jwtToAddress(jwt, salt)` 確定性地將 OAuth 身份映射到 Sui 地址。相同的使用者使用相同的鹽值始終獲得相同的地址,實現跨會話的持久錢包身份。
* **zkLogin 交易簽署:** 暫時性金鑰對簽署交易位元組,然後 `getZkLoginSignature` 將暫時性簽章與 ZK 證明和地址種子結合,產生網路接受的複合簽章。
## 架構
此範例有 4 個角色:一個 React 前端、一個 OAuth 提供者、一個 ZK prover 服務和 Sui 網路。沒有 Move 合約;應用程式發送原生的 SUI 轉帳交易。React 前端協調整個流程。它產生一個暫時性金鑰對,將使用者重新導向到彈出視窗中的 OAuth 提供者(Google 或 Apple),接收 JWT,將證明輸入發送到由 Mysten Labs 託管的 ZK prover 服務,並使用產生的證明透過 gRPC 在 Sui 網路上簽署和執行交易。
下圖追蹤了從金鑰對產生到交易執行的 1 個完整 zkLogin 流程。

以下步驟逐步解說該流程:
1. 使用者點擊 **Generate Key Pair**。前端從 Sui 網路獲取當前 Epoch,建立一個 Ed25519 暫時性金鑰對,產生隨機數,並計算 nonce。該金鑰對在當前 Epoch 之後的已設定的 Epoch 數量內有效。
2. 使用者點擊 **Login with Google**。前端打開一個彈出視窗到 OAuth 提供者,nonce 嵌入在授權 URL 中。使用者進行身份驗證後,提供者重新導向到應用程式的回呼 URL,URL hash 中包含 `id_token`。彈出視窗提取 token 並透過 `postMessage` 將其發送到父視窗。
3. 使用者點擊 **Generate ZK Proof**。前端將 JWT、擴展的暫時性公鑰、最大 Epoch、隨機數(base64 編碼)和鹽值發送到 Mysten Labs prover 服務。prover 返回零知識證明。前端然後使用 `jwtToAddress` 從 JWT 和鹽值推導 Sui 地址。
4. 使用者輸入接收者地址和 SUI 數量。前端建構一個 `splitCoins` + `transferObjects` 交易,使用暫時性金鑰對簽署它,使用 `getZkLoginSignature` 將簽章與 ZK 證明結合成 zkLogin 簽章,並透過 gRPC 執行交易。
### zkLogin 如何運作
zkLogin 系統使用零知識證明將 OAuth 身份(如 Google 帳號)映射到 Sui 地址。關鍵洞察是,使用者的 Sui 地址是從 2 個輸入確定性推導出來的:他們的 OAuth `sub` 宣告(來自提供者的唯一使用者識別碼)和一個應用程式特定的鹽值。相同的使用者使用相同的鹽值始終獲得相同的地址。
為了授權交易,使用者不使用長期私鑰簽署。相反,應用程式產生一個短期的暫時性金鑰對,將金鑰對的 nonce 嵌入 OAuth 登入請求中,並接收一個 JWT,該 JWT 將使用者的身份綁定到該暫時性金鑰。然後 ZK prover 服務產生一個證明,證明 JWT 是有效的並且對應於所宣告的地址,而無需將 JWT 本身揭示給網路。暫時性金鑰簽署交易位元組,ZK 證明包裝簽章,使得網路可以驗證授權而無需看到使用者的 OAuth 憑證。
更多詳情,請參見 [zkLogin 文件](/sui-stack/zklogin-integration/zklogin)。
## 先決條件
* 先決條件
* [安裝最新版本的 Sui](/getting-started/onboarding/sui-install)。
* [設定 Sui 客戶端](/getting-started/onboarding/configure-sui-client)。
* [建立一個 Sui 地址](/getting-started/onboarding/get-address)。
* [取得 SUI 測試網 token](/getting-started/onboarding/get-coins)。
* 下載並安裝一個 IDE。推薦以下 IDE,因為它們提供 Move 擴充:
* [VSCode](https://code.visualstudio.com/),對應的 [Move 擴充](https://marketplace.visualstudio.com/items?itemName=mysten.move)
* [Emacs](https://www.gnu.org/software/emacs/),對應的 [Move 擴充](https://github.com/amnn/move-mode)
* [Vim](https://www.vim.org/download.php),對應的 [Move 擴充](https://github.com/yanganto/move.vim)
* [Zed](https://zed.dev/),對應的 [Move 擴充](https://github.com/Tzal3x/move-zed-extension)
或者,你可以使用 [Move web IDE](https://www.playmove.dev/),它不需要下載。然而,它不支援本指南所需的所有功能。
* [下載並安裝 Git](https://git-scm.com/downloads)。
* [Node.js](https://nodejs.org/) 18 或更高版本
* [Rust 工具鏈](https://rustup.rs/)(用於 relayer 服務)
* 一個 Sui 錢包([Slush Wallet](https://slush.app/) 或其他相容的錢包)
* 一個 [Google OAuth 客戶端](https://console.cloud.google.com/)及其 client ID。你的 Google OAuth 客戶端必須將 `http://localhost:5173/` 設定為授權的 JavaScript 來源和授權的 redirect URI。
## 設定
按照以下步驟在本地設定此範例。
步驟 1:複製倉庫
```bash
$ git clone -b solution https://github.com/MystenLabs/sui-move-bootcamp.git
$ cd sui-move-bootcamp/K2
```
步驟 2:安裝依賴
```bash
$ pnpm install
$ pnpm install @mysten/utils
```
步驟 3:設定環境變數
```bash
$ cp .env.example .env
```
使用你的值編輯 `.env`:
[.env](https://github.com/MystenLabs/sui/blob/main/.env)
```bash
VITE_NETWORK=devnet
VITE_SUI_GRPC_URL=https://fullnode.devnet.sui.io:443
VITE_SALT=248191903847969014646285995941615069143
VITE_EPHEMERAL_KEY_DURATION_EPOCHS=2
VITE_OAUTH_PROVIDER_NAME=GOOGLE
VITE_OAUTH_CLIENT_ID=YOUR_GOOGLE_CLIENT_ID
```
將 `YOUR_GOOGLE_CLIENT_ID` 替換為來自你的 Google Cloud 專案的 OAuth client ID。`VITE_SALT` 可以是任何大的隨機數;它決定了推導出的 Sui 地址。持續使用相同的鹽值以獲取相同的地址。
## 執行範例
啟動前端:
```bash
$ pnpm dev
```
在瀏覽器中打開 `http://localhost:5173`。登入頁面解釋了什麼是 zkLogin。點擊 **Get Started** 進入 4 步驟精靈。依序完成每個步驟:產生金鑰對、使用 Google 登入、產生 ZK 證明,然後發送一個測試 SUI 轉帳以驗證完整流程。
## 關鍵程式碼重點
以下片段是值得仔細閱讀的程式碼部分。
### 暫時性金鑰對產生
`useEphemeral` hook 產生一個短期的 Ed25519 金鑰對,並計算將其綁定到 OAuth 會話的 nonce。
Loading…\
該 hook 建立一個 `Ed25519Keypair`,提取其公鑰,產生加密隨機數,獲取當前 Epoch 以計算 `maxEpoch`,並使用 `generateNonce` 推導 nonce。nonce 被嵌入到 OAuth URL 中,以便 JWT 被綁定到這個特定的暫時性金鑰。在 `maxEpoch` 過後,該金鑰對將無法再授權交易。
### OAuth 彈出視窗 token 提取
`useOauthPopup` hook 在 OAuth 回呼彈出視窗中執行,並將 JWT 發送回父視窗。
Loading…\
當 OAuth 提供者重新導向回應用程式時,彈出視窗檢測到它有 `window.opener`,從 URL hash 片段中解析 `id_token`,使用 `postMessage` 將其發送到父視窗,然後關閉自己。父視窗在 `useOauthLogin` hook 中監聽此訊息。
### ZK 證明獲取
`useZkProof` hook 準備證明酬載並從 Mysten Labs prover 服務獲取它。
Loading…\
酬載包含 JWT、擴展的暫時性公鑰(編碼了金鑰型別和位元組)、最大 Epoch、base64 編碼的隨機數和鹽值,以及 key claim 名稱(`sub`)。prover 驗證 JWT 簽章並返回一個 ZK 證明,前端將其儲存以備交易簽署使用。
### 從 JWT 推導地址
`useWallet` hook 從 JWT 和鹽值推導確定性的 Sui 地址。
Loading…\
`jwtToAddress` 將 JWT 的 `sub` 宣告和鹽值映射到一個 Sui 地址。`genAddressSeed` 產生後續 zkLogin 簽章所需的地址種子。相同的 JWT `sub` + 鹽值始終產生相同的地址,因此使用者的錢包跨會話持續存在。
### 交易建構和 zkLogin 執行
`suiWriteClient` 建構一個 SUI 轉帳交易並使用 zkLogin 簽章執行它。
Loading…\
該函數並行獲取可用的代幣和參考 gas 價格,選擇最大的代幣用於 gas,建構一個 `splitCoins` + `transferObjects` 交易,並使用暫時性金鑰對簽署它。然後 `useLiveTransaction` hook 使用 `getZkLoginSignature` 將此簽章與 ZK 證明結合,產生最終的 zkLogin 簽章,然後呼叫 `executeZkLoginTransaction`。
### ZK 證明獲取工具
`fetchZkProof` 工具呼叫 Mysten Labs prover 端點。
Loading…\
該函數將證明酬載 POST 到 `https://prover-dev.mystenlabs.com/v1`。錯誤處理區分了網路故障(可能表示 CORS 問題)和 prover 錯誤。對於正式應用程式,考慮透過後端代理 prover 請求以避免 CORS 限制。
## 常見修改
* **新增會話持久性:** 將暫時性金鑰對、JWT 和 ZK 證明儲存在 `localStorage` 中,這樣使用者無需在頁面重新載入時重新驗證。檢查 `maxEpoch` 相對於當前 Epoch 以確定會話是否仍然有效。
* **支援多個 OAuth 提供者:** `getOauthUrl` 工具已經處理了 Google 和 Apple。透過使用 nonce 建構適當的授權 URL 來為 Facebook、Twitch 或其他 OpenID Connect 提供者新增情況。
* **新增 sponsored transactions:** 不要要求 zkLogin 錢包持有 SUI 用於 gas,而是整合一個 gas station 服務來贊助交易。建構具有單獨 gas 擁有者的交易,並讓贊助者共同簽署。
## 故障排除
以下章節解決此範例中的常見問題。
### OAuth 彈出視窗被封鎖
**症狀:** 點擊 **Login with Google** 沒有任何反應,或者瀏覽器顯示彈出視窗被封鎖的通知。
**原因:** 瀏覽器的彈出視窗阻擋器阻止了 OAuth 視窗打開。
**修復:** 在你的瀏覽器設定中允許 `localhost` 的彈出視窗,或點擊網址列中的彈出視窗被封鎖圖示以允許彈出視窗。`openOauthPopup` 函數在彈出視窗無法打開時會拋出錯誤。
### Google 登入失敗
**症狀:** Google OAuth 彈出視窗立即關閉或從未出現。
**原因:** `VITE_OAUTH_CLIENT_ID` 設定錯誤,redirect URI 與 `http://localhost:5173/` 不匹配,或第三方 cookie 被封鎖。
**修復:** 在 Google Cloud Console 中驗證 Google Client ID 和授權的 redirect URI。嘗試不同的瀏覽器或停用 cookie 阻擋擴充。
### 在 URL hash 中找不到 `id_token`
**症狀:** OAuth 彈出視窗打開並進行了身份驗證,但父視窗從未收到 JWT token。
**原因:** Google Cloud console 中的 OAuth redirect URI 與應用程式的 URL 不匹配,或 response type 未設定為 `id_token`。
**修復:** 驗證 Google Cloud console 中的授權 redirect URI 精確匹配 `http://localhost:5173`。確保 OAuth URL 包含 `response_type=id_token` 和 `scope=openid`。
### ZK 證明獲取因 CORS 錯誤失敗
**症狀:** 瀏覽器控制台在呼叫 prover 服務時顯示 CORS 錯誤。
**原因:** `prover-dev.mystenlabs.com` 端點可能不允許來自 `localhost` 或你的網域的請求。
**修復:** 使用後端代理將請求轉發到 prover 服務,或在 prover 允許的網域上執行應用程式。對於開發,一個停用 CORS 的瀏覽器擴充可以作為臨時的解決方法。
### 推導出的地址沒有餘額
**症狀:** 錢包地址成功推導出來,但顯示 0 SUI 餘額。
**原因:** 推導出的地址尚未收到任何 SUI。該地址是確定性的,但開始時沒有資金。
**修復:** 使用 [Sui faucet](https://faucet.sui.io/) 請求開發網。從步驟 3 面板複製地址,並將其貼入 faucet。
### 交易因 `InvalidSignature` 失敗
**症狀:** 交易已提交,但網路以簽章錯誤拒絕它。
**原因:** 暫時性金鑰對已過期(當前 Epoch 超過 `maxEpoch`),或 JWT 中的 nonce 與用於簽署的暫時性金鑰不匹配。
**修復:** 從步驟 1 重新開始流程。產生一個新的暫時性金鑰對,重新驗證以獲取帶有新 nonce 的全新 JWT,並產生新的 ZK 證明。如果金鑰過期太快,增加 `VITE_EPHEMERAL_KEY_DURATION_EPOCHS`。