zing-cli 0.0.2

CLI tool + MCP server for paid semantic search on Zing
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024

# ZkLogin Integration

[Source URL](https://docs.sui.io/sui-stack/zklogin-integration/)

Here is the high-level flow the wallet or frontend application must implement to support  **ZkLogin enabled transactions:**

1. The wallet creates an ephemeral key pair.
2. The wallet prompts the user to complete an OAuth login flow with the nonce corresponding to the ephemeral public key.
3. After receiving the JSON Web Token (JWT), the wallet obtains a zero-knowledge proof.
4. The wallet obtains a unique user salt based on a JWT. Use the OAuth subject identifier and salt to compute the zkLogin Sui address.
5. The wallet signs transactions with the ephemeral private key.
6. The wallet submits the transaction **Transaction** A number of commands that execute on inputs to define the result of the transaction. with the ephemeral signature and the zero-knowledge proof.\
   The following sections cover the specific implementation details.

## Install thezkLogin TypeScript SDK

To use the zkLogin TypeScript SDK in your project, run the following command in your project root:

* npm
* Yarn
* pnpm

```sh
$ npm install @mysten/sui
```

```sh
$ yarn add @mysten/sui
```

```sh
$ pnpm add @mysten/sui
```

If you want to use the latest experimental version:

* npm
* Yarn
* pnpm

```sh
$ npm install @mysten/sui@experimental
```

```sh
$ yarn add @mysten/sui@experimental
```

```sh
$ pnpm add @mysten/sui@experimental
```

## Get JWT

1. Generate an ephemeral key pair. Follow the same process as you would generating a key pair in a traditional wallet. See [Sui SDK](https://sdk.mystenlabs.com/typescript/cryptography/keypairs) for details.
2. Set the expiration time for the ephemeral key pair. The wallet decides whether the maximum epoch **Epoch** A period of time defined by the network. is the current epoch or later. The wallet also determines whether this is adjustable by the user.
3. Assemble the OAuth URL with configured client ID, redirect URL, ephemeral public key and nonce: This is what the application sends the user to complete the login flow with a computed nonce.

```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);
```

The auth flow URL can be constructed with `$CLIENT_ID` , `$REDIRECT_URL` and `$NONCE` .

For some providers (Yes for Auth Flow Only), the JWT can be found immediately in the redirect URL after the auth flow.

For other providers (No for Auth Flow Only), the auth flow only returns a code ( `$AUTH_CODE` ) in redirect URL. To retrieve the JWT, an additional POST call is required with the token exchange URL.

| **Provider** | **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                                                                                                                                 | Yes                |
| 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                                                                                                                                 | Yes                |
| 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                                                                                                                                 | Yes                |
| 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` | No                 |
| 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                                                                                                                                 | Yes                |
| 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`                      | Yes                |
| 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`                   | Yes                                                                                                                                 |                    |

## Decoding JWT

Upon successful redirection, the OpenID provider attaches the JWT as a URL parameter. The following is an example using the Google flow.

```text
http://host/auth?id_token=tokenPartA.tokenPartB.tokenPartC&authuser=0&prompt=none
```

The `id_token` param is the JWT in encoded format. You can validate the correctness of the encoded token and investigate its structure by pasting it in the [jwt.io](https://jwt.io) website.

To decode the JWT you can use a library like `jwt_decode` and map the response to the provided type `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;
}
```

## User salt management

zkLogin uses the user salt to compute the zkLogin Sui address (see [definition](https://docs.sui.io/sui-stack/zklogin-integration)). The salt must be a 16-byte value or an integer smaller than `2n**128n`. There are several options for the application to maintain the user salt:

1. Client side:
   * Option 1: Request user input for the salt during wallet access, transferring the responsibility to the user, who must then remember it.
   * Option 2: Browser or Mobile Storage: Ensure proper workflows to prevent users from losing wallet access during device or browser changes. One approach is to email the salt during new wallet setup.
2. Backend service that exposes an endpoint that returns a unique salt for each user consistently.
   * Option 3: Store a mapping from user identifier (for example, `sub`) to user salt in a conventional database (for example, `user` or `password` table). The salt is unique per user.
   * Option 4: Implement a service that keeps a master seed value, and derive a user salt with key derivation by validating and parsing the JWT. For example, use `HKDF(ikm = seed, salt = iss || aud, info = sub)` defined [here](https://github.com/MystenLabs/fastcrypto/blob/e6161f9279510e89bd9e9089a09edc018b30fbfe/fastcrypto/src/hmac.rs#L121). This option does not allow any rotation on master seed or change in client ID (that is, aud), otherwise a different user address is derived and results in loss of funds.

The following is an example request and response for the Mysten Labs-maintained salt server (using option 4). If you want to use the Mysten Labs salt server, refer to the [Enoki docs](https://docs.enoki.mystenlabs.com/) and contact us. Only valid JWT authenticated with whitelisted client IDs are accepted.

```javascript
curl -X POST https://salt.api.mystenlabs.com/get_salt -H 'Content-Type: application/json' -d '{"token": "$JWT_TOKEN"}'
```

```javascript
Response: {"salt":"129390038577185583942388216820280642146"}
```

User salt is used to disconnect the OAuth identifier (sub) from the onchain Sui address to avoid linking Web2 credentials with Web3 credentials. While losing or misusing the salt could enable this link, it wouldn't compromise fund control or zkLogin asset authority.

## Get the user's Suiaddress

Once the OAuth flow completes, the JWT can be found in the redirect URL. Along with the user salt, thezkLoginaddress can be derived as follows:

```typescript
import { jwtToAddress } from '@mysten/sui/zklogin';

const zkLoginUserAddress = jwtToAddress(jwt, userSalt, false);
```

## Get the zero-knowledge proof

The next step is to fetch the ZK proof. This is an attestation (proof) over the ephemeral key pair that proves the ephemeral key pair is valid.

First, generate the extended ephemeral public key to use as an input to the ZKP.

```typescript
import { getExtendedEphemeralPublicKey } from '@mysten/sui/zklogin';

const extendedEphemeralPublicKey = getExtendedEphemeralPublicKey(ephemeralKeyPair.getPublicKey());
```

You need to fetch a new ZK proof if the previous ephemeral key pair is expired or is otherwise inaccessible.

Because generating a ZK proof can be resource-intensive and potentially slow on the client side, wallets should use a backend service endpoint dedicated to ZK proof generation.

There are two options:

1. Call the Mysten Labs-maintained proving service.
2. Run the proving service in your backend using the provided Docker images.

### Call the Mysten Labs-maintained proving service

If you want to use the Mysten hosted ZK Proving Service for Mainnet **Mainnet** Production network for live transactions and real-value assets. refer to the [Enoki docs](https://docs.enoki.mystenlabs.com/) and contact us for access.

You can use BigInt or Base64 encoding for `extendedEphemeralPublicKey` , `jwtRandomness` , and `salt` . The following examples show two sample requests with the first using BigInt encoding and the second using 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" \
}'
```

Response:

```json
{
	"proofPoints": {
		"a": [
			"17267520948013237176538401967633949796808964318007586959472021003187557716854",
			"14650660244262428784196747165683760208919070184766586754097510948934669736103",
			"1"
		],
		"b": [
			[
				"21139310988334827550539224708307701217878230950292201561482099688321320348443",
				"10547097602625638823059992458926868829066244356588080322181801706465994418281"
			],
			[
				"12744153306027049365027606189549081708414309055722206371798414155740784907883",
				"17883388059920040098415197241200663975335711492591606641576557652282627716838"
			],
			["1", "0"]
		],

		"c": [
			"14769767061575837119226231519343805418804298487906870764117230269550212315249",
			"19108054814174425469923382354535700312637807408963428646825944966509611405530",
			"1"
		]
	},
	"issBase64Details": {
		"value": "wiaXNzIjoiaHR0cHM6Ly9pZC50d2l0Y2gudHYvb2F1dGgyIiw",
		"indexMod4": 2
	},
	"headerBase64": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEifQ"
}
```

### How to handle CORS error

To avoid possible CORS errors in Frontend apps, it is suggested to delegate this call to a backend service.

The response can be mapped to the inputs parameter type of `getZkLoginSignature` of zkLogin SDK.

```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;
```

### Run the proving service in your backend

1. Install [Git Large File Storage](https://git-lfs.com/) (an open source Git extension for large file versioning) before downloading the zkey.
2. Download the [Groth16 proving key zkey file](https://docs.circom.io/getting-started/proving-circuits/) . There are zkeys available for all Sui networks.
3.

* Main zkey (for Mainnet and Testnet)

```sh
$ wget -O - https://raw.githubusercontent.com/sui-foundation/zklogin-ceremony-contributions/main/download-main-zkey.sh | bash
```

* Test zkey (for Devnet)

```sh
$ wget -O - https://raw.githubusercontent.com/sui-foundation/zklogin-ceremony-contributions/main/download-test-zkey.sh | bash
```

* To verify the download contains the correct zkey file, you can run the following command to check the Blake2b hash: `b2sum ${file_name}.zkey`.

| **Network**      | **zkey file name**  | **Hash**                                                                                                                           |
| ---------------- | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| Mainnet, Testnet | `zkLogin-main.zkey` | `060beb961802568ac9ac7f14de0fbcd55e373e8f5ec7cc32189e26fb65700aa4e36f5604f868022c765e634d14ea1cd58bd4d79cef8f3cf9693510696bcbcbce` |
| Devnet           | `zkLogin-test.zkey` | `686e2f5fd969897b1c034d7654799ee2c3952489814e4eaaf3d7e1bb539841047ae8ee5fdcdaca5f4ddd76abb5a8e8eb77b44b693a2ba9d4be57e94292b26ce2` |

3\. For the next step, you need two Docker images from the [mysten/zklogin repository](https://hub.docker.com/r/mysten/zklogin) (tagged as `prover` and `prover-fe`). A docker compose file is available that automates this process. Run `docker compose` with the downloaded zkey from the same directory as the YAML file.

```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. To call the service, the following two endpoints are supported:

* `/ping` : To test if the service is up. Running `curl http://localhost:PROVER_PORT/ping` should return `pong` .
* `/v1` : The request and response are the same as the Mysten Labs maintained service.

2. `/ping` : To test if the service is up. Running `curl http://localhost:PROVER_PORT/ping` should return `pong` .
3. `/v1` : The request and response are the same as the Mysten Labs maintained service.\
   Keep the following considerations in mind:

* The backend service (mysten / zklogin :prover-stable ) is compute-heavy. Use at least the minimum recommended 16 cores and 16GB RAM. Using weaker instances can lead to timeout errors with the message "Call to rapidsnark service took longer than 15s". You can adjust the environment variable `PROVER_TIMEOUT` to set a different timeout value, for example, `PROVER_TIMEOUT=30` for a timeout of 30 seconds.
* If you want to compile the prover from scratch (for performance reasons), see the fork of [rapidsnark](https://github.com/MystenLabs/rapidsnark#compile-prover-in-server-mode) . You need to compile and launch the prover in server mode.
* Setting `DEBUG=*` turns on all logs in the prover-fe service, some of which might contain PII. Consider using `DEBUG=zkLogin:info,jwks` in production environments.

## Assemble thezkLogin signature and submit thetransaction

First, sign thetransaction bytes with the ephemeral private key using the key pair generated previously. This is the same as [traditional key pair signing](https://sdk.mystenlabs.com/typescript/cryptography/keypairs) . Make sure that thetransaction`sender` is also defined.

```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
});
```

Next, generate anaddress seed by combining `userSalt` , `sub` (subject ID), and `aud` (audience).

Set theaddress seed and the partialzkLogin signature to be the `inputs` parameter.

You can now serialize thezkLogin signature by combining the ZK proof ( `inputs` ), the `maxEpoch` , and the ephemeral signature ( `userSignature` ).

```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,
});
```

Finally, execute the transaction .

```typescript
client.executeTransaction({
	transaction: bytes,
	signatures: [zkLoginSignature],
});
```

## Caching the ephemeral private key and ZK proof

As previously documented, each ZK proof is tied to an ephemeral key pair. So you can reuse the proof to sign any number of transactions until the ephemeral key pair expires (until the current epoch crosses `maxEpoch` ).

You might want to cache the ephemeral key pair along with the ZKP for future uses.

However, the ephemeral key pair needs to be treated as a secret akin to a key pair in a traditional wallet. This is because if both the ephemeral private key and ZK proof are revealed to an attacker, then they can typically sign any transaction on behalf of the user (using the same process described previously).

Consequently, you should not store them persistently in a storage location that is not secure, on any platform. For example, on browsers, use session storage instead of local storage to store the ephemeral key pair and the ZK proof. This is because session storage automatically clears its data when the browser session ends, while data in local storage persists indefinitely.

## Efficiency considerations

Compared to traditional signatures,zkLogin signatures take a longer time to generate. For example, the prover that Mysten Labs maintains typically takes about three seconds to return a proof, which runs on a machine with 16 vCPUs and 64 GB RAM. Using more powerful machines, such as those with physical CPUs or graphics processing units (GPUs), can reduce the proving time further.

Carefully consider how many requests your application needs to make to the prover. Broadly speaking, the right metric to consider is the number of active user sessions and not the number of signatures. This is because you can cache the same ZK proof and reuse it across the session, as previously explained. For example, if you expect a million active user sessions per day, then you need a prover that can handle one or two requests per second (RPS), assuming evenly distributed traffic.

The prover that Mysten Labs maintains is set to auto-scale to handle traffic surges. If you are not sure whether Mysten Labs can handle a specific number of requests or expect a sudden spike in the number of prover requests your application needs to make, reach out to us on [Discord](https://discord.gg/sui) . The plan is to horizontally scale the prover to handle any RPS you require.

***

# What is zkLogin?

[Page URL](https://docs.sui.io/sui-stack/zklogin-integration/zklogin)

**zkLogin:** A Sui primitive that allows you to send transactions using an OAuth credential. is a Sui primitive that allows you to send transactions from a Sui address. using an OAuth credential without publicly linking the two.

zkLogin is designed with the following goals:

* **Streamlined onboarding:** zkLogin enables you to transact on Sui using the familiar OAuth login flow, removing the need to handle cryptographic keys or remember mnemonics.
* **Self-custody:** A zkLogin transaction **Transaction** A number of commands that execute on inputs to define the result of the transaction. requires user approval through the standard OAuth login process. The OAuth provider cannot transact on your behalf.
* **Security:** zkLogin is a two-factor authentication scheme. Sending a transaction requires both a credential from a recent OAuth login and a salt not managed by the OAuth provider. An attacker who compromises an OAuth account cannot transact from your Sui address unless they also compromise the salt.
* **Privacy:** Zero-knowledge proofs prevent third parties from linking a Sui address with its corresponding OAuth identifier.
* **Optional verified identity:** You can opt in to verify the OAuth identifier used to derive a particular Sui address . This creates the foundation for a verifiable onchain identity layer.
* **Accessibility:** zkLogin integrates with other Sui primitives, such as sponsored transactions **Sponsored transaction** When one address pays the gas fee for a transaction submitted by another address. and multisig **Multisig** Multi-signature transactions that require multiple keys for authorization. .
* **Rigor:** The code for zkLogin has been independently [audited](https://github.com/sui-foundation/security-audits/blob/main/docs/zksecurity_zklogin-circuits.pdf) by two firms specializing in zero-knowledge. The public zkLogin ceremony for creating the common reference string included contributions from more than 100 participants.\
  The key differentiators that zkLogin brings to Sui are:

1. Native support in Sui: Unlike other solutions that are blockchain agnostic, zkLogin is deployed just for Sui. This means a zkLogin transaction can be combined with multisig and sponsored transactions seamlessly.
2. Self-custodial without additional trust: Sui leverages the nonce field in JWT to commit to ephemeral public key, so no persistent private key management is required with any trusted parties. The JWK itself is an oracle agreed upon by the quorum **Quorum** A set of validators whose combined voting power is greater than 2/3 of the total. of stakes by the validators without trusting any source of authority.
3. Full privacy: Nothing is required to submit onchain except the zero-knowledge proof and the ephemeral signature.
4. Compatible with existing identity providers: zkLogin is compatible with providers that adopt OpenID Connect, meaning you do no need to trust any intermediate identity issuers or verifiers other than the OAuth providers themselves.\
   If you are a builder who wants to integrate zkLogin into your application or wallet, see the [zkLogin integration guide](/sui-stack/zklogin-integration) .

## **OpenID providers**

The following table lists the OpenID providers that can support zkLogin or are currently being reviewed to determine whether they can support zkLogin.

| **Provider**   | **Can support?** | **Devnet** | **Testnet** | **Mainnet** |
| -------------- | ---------------- | ---------- | ----------- | ----------- |
| Facebook       | Yes              | Yes        | Yes         | Yes         |
| Google         | Yes              | Yes        | Yes         | Yes         |
| Twitch         | Yes              | Yes        | Yes         | Yes         |
| Apple          | Yes              | Yes        | Yes         | Yes         |
| Slack          | Yes              | Yes        | No          | No          |
| Kakao          | Yes              | Yes        | No          | No          |
| Microsoft      | Yes              | Yes        | No          | No          |
| AWS (Tenant)\* | Yes              | Yes        | Yes         | Yes         |
| Karrier One    | Yes              | Yes        | Yes         | Yes         |
| Credenza3      | Yes              | Yes        | Yes         | Yes         |
| RedBull        | Under review     | No         | No          | No          |
| Amazon         | Under review     | No         | No          | No          |
| WeChat         | Under review     | No         | No          | No          |
| Auth0          | Under review     | No         | No          | No          |
| Okta           | Under review     | No         | No          | No          |

* Sui supports AWS (Tenant) but the provider is enabled per tenant. Contact us for more information.

## Terminology and notations

This section describes relevant OpenID terminology defined in [the OpenID specification](https://openid.net/specs/openid-connect-core-1_0#Terminology) and how they are used in zkLogin , along with definitions for protocol details.

### OpenID provider (OP)

An OpenID provider is an OAuth 2.0 authorization server that is capable of authenticating an end-user and providing claims to a relying party about the authentication event and the end-user. This is identified in the `iss` field in JSON web token payload. Check the table of available OPs for the entitieszkLogin currently supports.

### Relying party (RP) or client

A relying party is an OAuth 2.0 client application that requires end-user authentication and claims from an OpenID provider. This is assigned by an OP when you create an application. This is identified in the `aud` field in JWT payload and refers to any zkLogin enabled wallet or application.

### Subject identifier (sub)

The subject identifier is a locally unique identifier within the issuer for the end user, which the RP is intended to consume. Sui uses this as the key claim to derive a user address .

### JSON Web Key (JWK)

A JSON Web Key is a JSON data structure that represents a set of public keys for an OP. A public endpoint (as in <https://www.googleapis.com/oauth2/v3/certs> ) can be queried to retrieve the valid public keys corresponding to `kid` for the provider. Upon matching with the `kid` in the header of a JWT, the JWT can be verified against the payload and its corresponding JWK. In Sui, all authorities call the JWK endpoints independently, and the latest view of JWKs for all supported providers is updated during protocol upgrades. The correctness of JWKs is guaranteed by the quorum (2f+1) of validator **Validator** Responsible for executing tasks like staking, gas price references, and tallying rules. stake.

### JSON Web Token (JWT)

A JSON Web Token is in the redirect URI to the RP after you complete the OAuth login flow (as in `https://redirect.com?id_token=$JWT_TOKEN` ). The JWT contains a `header` , `payload` , and a `signature` . The signature is an RSA signature verified against `jwt_message = header + . + payload` and its JWK identified by `kid` . The `payload` contains a JSON of many claims that is a name-value pair.

**Header**

| **Name** | **Example Value**                        | **Usage**                                                 |
| -------- | ---------------------------------------- | --------------------------------------------------------- |
| `alg`    | RS256                                    | zkLogin only supports RS256 (RSA + SHA-256).              |
| `kid`    | c3afe7a9bda46bae6ef97e46c95cda48912e5979 | Identifies the JWK that should be used to verify the JWT. |
| `typ`    | JWT                                      | zkLogin only supports JWT.                                |

**Payload**

| **Name** | **Example Value**                                                                                                                                           | **Usage**                                                                                                                                                  |
| -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `iss`    | <https://accounts.google.com>                                                                                                                               | A unique identifier assigned to the OAuth provider.                                                                                                        |
| `aud`    | [575519200000-msop9ep45u2uo98hapqmngv8d8000000.apps.googleusercontent.com](http://575519200000-msop9ep45u2uo98hapqmngv8d8000000.apps.googleusercontent.com) | A unique identifier assigned to the relying party by the OAuth provider.                                                                                   |
| `nonce`  | hTPpgF7XAKbW37rEUS6pEVZqmoI                                                                                                                                 | A value set by the relying party. The zkLogin enabled wallet is required to set this to the hash of ephemeral public key, an expiry time and a randomness. |
| `sub`    | 110463452167303000000                                                                                                                                       | A unique identifier assigned to the user.                                                                                                                  |

For a zkLogin transaction, the `iat` and `exp` claims (timestamp) are not used. Instead, the `nonce` specifies expiry times.

### Key claim

The key claim used to derive your address , such as `sub` or `email` . Naturally, it's ideal to use claims that are fixed once and never changed again. zkLogin currently supports `sub` as the key claim because the OpenID specification mandates that providers do not change this identifier.

### Notations

1. `(eph_sk, eph_pk)` : The private and public key pair used to produce ephemeral signatures. The signing mechanism is the same as traditional transaction signing, but it is ephemeral because it is only stored for a short session and can be refreshed upon new OAuth sessions. The ephemeral public key is used to compute the `nonce` .
2. `nonce` : An application-defined field embedded in the JWT payload, computed as the hash of the ephemeral public key, JWT randomness, and the maximum epoch **Epoch** A period of time defined by the network. (Sui's defined expiry epoch ). Specifically, a zkLogin compatible nonce is required to passed in as `nonce = ToBase64URL(Poseidon_BN254([ext_eph_pk_bigint / 2^128, ext_eph_pk_bigint % 2^128, max_epoch, jwt_randomness]).to_bytes()[len - 20..])` where `ext_eph_pk_bigint` is the BigInt representation of `ext_eph_pk` .
3. `ext_eph_pk` : The byte representation of an ephemeral public key ( `flag || eph_pk` ). Size varies depending on the choice of the signature scheme (denoted by the flag, defined in [Signatures](/develop/transactions/transaction-auth/auth-overview) ).
4. `user_salt` : A value introduced to unlink the OAuth identifier with the onchain address .
5. `max_epoch` : The epoch at which the JWT expires. This is `u64` used in Sui.
6. `kc_name` : The key claim name, for example `sub` .
7. `kc_value` : The key claim value, for example `110463452167303000000` .
8. `hashBytesToField(str, maxLen)` : Hashes the ASCII string to a field element using the [Poseidon hash](https://eprint.iacr.org/2019/458.pdf) .

## Entities

1. Application frontend: This describes the wallet or frontend application that supports zkLogin . The frontend is responsible for storing the ephemeral private key, directing you to complete the OAuth login flow, and creating and signing a zkLogin transaction .
2. Salt backup service: This is a backend service responsible for returning a salt per unique user. See [zkLogin Integration Guide](/sui-stack/zklogin-integration) for other strategies to maintain salt.
3. Zero-knowledge proving service: This is a backend service responsible for generating zero-knowledge proofs based on JWT, JWT randomness, user salt, and max epoch . This proof is submitted onchain along with the ephemeral signature for a zkLogin transaction .

## HowzkLogin works

At a high level, the zkLogin protocol works as follows:

1. A JWT is a signed payload from OAuth providers that includes a user-defined field named nonce. zkLogin uses [the OpenID Connect OAuth flow](https://openid.net/developers/how-connect-works/) by defining the nonce as a public key and an expiry epoch .
2. The wallet stores an ephemeral key pair, where the ephemeral public key is defined in the nonce. The ephemeral private key signs transactions for a short session. A Groth16 zero-knowledge proof is generated from the JWT, which conceals sensitive fields.
3. A transaction is submitted onchain with the ephemeral signature and the zero-knowledge proof. Sui authorities execute the transaction after verifying the ephemeral signature and the proof.
4. Instead of deriving the Su iaddress based on a public key, the zkLogin address is derived from `sub` (user identifier), `iss` (provider), `aud` (application), and `user_salt` (a value that unlinks the OAuth identifier from the onchain address ).

![zkLogin flow diagram](assets-0.webp)

#### Step 0:zkLogin uses Groth16 for zkSNARK instantiation, which requires a common reference string (CRS) linked to the circuit.

A ceremony generates the CRS, which is used to produce the proving key in the proving service and the verifying key in Sui authorities.

#### Steps 1-3: Login to an OpenID provider (OP) to obtain a JWT containing a nonce.

An ephemeral key pair `(eph_sk, eph_pk)` is generated and `eph_pk` , expiry times ( `max_epoch` ), and randomness ( `jwt_randomness` ) are embedded into the nonce. After login, the JWT appears in the redirect URL in the application.

#### Steps 4-5: The application frontend sends the JWT to a salt service. The service returns the unique `user_salt` based on `iss` , `aud` , and `sub` .

#### Steps 6-7: Send the JWT, user salt, ephemeral public key, JWT randomness, and key claim name (for example, `sub` ) to the proving service.

The proving service generates a zero-knowledge proof that:

* Confirms the nonce is derived correctly.
* Confirms the key claim value matches the corresponding JWT field.
* Verifies the RSA signature from the provider on the JWT.
* Confirms theaddress is consistent with the key claim and user salt.

![zkLogin authority](assets-1.webp)

#### Step 8: The application computes youraddress based on `iss` , `aud` , and `sub` .

#### Steps 9-10: Sign thetransaction with the ephemeral private key and submit it with the ephemeral signature, ZK proof, and other inputs to Sui.

After Step 10, Sui authorities verify the ZK proof against the provider's JWKs (stored by consensus **Consensus** The process by which the majority of nodes agree on the current state of the network ) and the ephemeral signature.

## Address definition

The address is computed on the following inputs:

1. The address flag: `zk_login_flag = 0x05` for zkLogin address . This serves as a domain separator.
2. `kc_name_F = hashBytesToField(kc_name, maxKCNameLen)` : Name of the key claim, for example `sub` . The sequence of bytes is mapped to a field element in BN254 using `hashBytesToField` (defined below).
3. `kc_value_F = hashBytesToField(kc_value, maxKCValueLen)` : The value of the key claim mapped using `hashBytesToField` .
4. `aud_F = hashBytesToField(aud, maxAudValueLen)` : The relying party (RP) identifier.
5. `iss` : The OpenID Provider (OP) identifier.
6. `user_salt` : A value introduced to unlink the OAuth identifier with the onchain address .\
   Finally, Sui derives `zk_login_address = Blake2b_256(zk_login_flag, iss_L, iss, addr_seed)` where `addr_seed = Poseidon_BN254(kc_name_F, kc_value_F, aud_F, Poseidon_BN254(user_salt))` .

## Ceremony

To preserve privacy of the OAuth artifacts, a zero-knowledge proof of possession is provided. zkLogin employs the Groth16 [zkSNARK](https://en.wikipedia.org/wiki/Non-interactive_zero-knowledge_proof) to instantiate the zero-knowledge proofs, as it is the most efficient general-purpose zkSNARK in terms of proof size and verification efficiency.

However, Groth16 needs a computation-specific common reference string (CRS) to be setup by a trusted party. With zkLogin expected to ensure the safe-keeping of high value transactions and the integrity of critical smart contracts, you cannot base the security of the system on the honesty of a single entity. Hence, to generate the CRS for the zkLogin circuit, it is vital to run a protocol which bases its security on the assumed honesty of a small fraction of a large number of parties.

The Sui zkLogin ceremony is a cryptographic multi-party computation (MPC) performed by a diverse group of participants to generate the CRS. The ceremony follows the MPC protocol [MMORPG](https://eprint.iacr.org/2017/1050.pdf) described by Bowe, Gabizon, and Miers. The protocol proceeds in 2 phases. The first phase results in a series of powers of a secret quantity tau in the exponent of an elliptic curve element. Because this phase is circuit-agnostic, the ceremony adopts the result of the existing community contributed [perpetual powers of tau](https://github.com/privacy-scaling-explorations/perpetualpowersoftau/tree/master) . The ceremony is the second phase, which is specific to the zkLogin circuit.

The MMORPG protocol is a sequential protocol, which allows an indefinite number of parties to participate in sequence, without the need of any prior synchronization or ordering. Each party needs to download the output of the previous party, generate entropy of its own and then layer it on top of the received result, producing its own contribution which is then relayed to the next party. The protocol guarantees security if at least 1 of the participants follows the protocol faithfully, generates strong entropy, and discards it reliably.

### How was the ceremony performed?

Invitations were sent to more than 100 people with diverse backgrounds and affiliations: Sui validators, cryptographers, Web3 experts, world-renowned academicians, and business leaders. The ceremony was planned to take place on the dates September 12-18, 2023, but allowed participants to join when they wanted with no fixed slots.

Because the MPC is sequential, each contributor needed to wait until the previous contributor finished in order to receive the previous contribution, follow the MPC steps and produce their own contribution. Due to this structure, a queue was provisioned where participants waited, while those who joined before them finished. To authenticate participants, a unique activation code was sent to each of them. The activation code was the secret key of a signing key pair, which had a dual purpose: it allowed the coordination server to associate the participant's email with the contribution, and to verify the contribution with the corresponding public key.

Participants could contribute through a browser or Docker. The browser option was more user-friendly for contributors to participate as everything happens in the browser. The Docker option required Docker setup but was more transparent because the Dockerfile and contributor source code are open source and the whole process is verifiable. Moreover, the browser option utilizes [snarkjs](https://github.com/iden3/snarkjs) while the Docker option utilizes [Kobi's implementation](https://github.com/iseriohn/phase2-bn254) . This provided software variety and contributors could choose to contribute by whichever method they trusted. In addition, participants could generate entropy through entering random text or making random cursor movements.

The zkLogin circuit and the ceremony client [code](https://github.com/sui-foundation/zk-ceremony-client) were made open source and the links were made available to the participants to review before the ceremony. In addition, the developer documentation and an [audit report](https://github.com/sui-foundation/security-audits/blob/main/docs/zksecurity_zklogin-circuits.pdf) on the circuit from zkSecurity were posted. The ceremony adopted [challenge #0081](https://pse-trusted-setup-ppot.s3.eu-central-1.amazonaws.com/challenge_0081) (resulting from 80 community contributions) from [perpetual powers of tau](https://github.com/privacy-scaling-explorations/perpetualpowersoftau/tree/master/0080_carter_response) in phase 1, which is circuit agnostic. The output of the [Drand](http://drand.love) random beacon at epoch #3298000 was applied to remove bias. For phase 2, the ceremony had 111 contributions, 82 from browser and 29 from docker. Finally, the output of the Drand random beacon at epoch #3320606 was applied to remove bias from contributions. All intermediate files can be reproduced following instructions for [phase 1](https://github.com/sui-foundation/zklogin-ceremony-contributions/blob/main/phase1/README.md) and [phase 2](https://github.com/sui-foundation/zklogin-ceremony-contributions/blob/main/phase2/README.md) .

The final CRS along with the transcript of contribution of every participant is available in a public repository. Contributors received both the hash of the previous contribution they were working on and the resulting hash after their contribution, displayed on-screen and sent through email. They can compare these hashes with the transcripts publicly available on the ceremony site. In addition, anyone is able to check that the hashes are computed correctly and each contribution is properly incorporated in the finalized parameters.

Eventually, the final CRS was used to generate the proving key and verifying key. The proving key is used to generate zero-knowledge proof for zkLogin , stored with the zero-knowledge proving service. The verifying key was [deployed](https://github.com/MystenLabs/sui/pull/13822) as part of the validator software (protocol version 25 in [release 1.10.1](https://github.com/MystenLabs/sui/releases/tag/mainnet-v1.10.1) ) that is used to verify the zkLogin transaction on Sui.

## Security and privacy

The following sections walk through all zkLogin artifacts, their security assumptions, and the consequences of loss or exposure.

### JWT

The JWT's validity is scoped on the client ID ( `aud` ) to prevent phishing attacks. The same origin policy for the proof prevents the JWT obtained for a malicious application from being used for zkLogin . The JWT for the client ID is sent directly to the application frontend through the redirect URL. A leaked JWT for the specific client ID can compromise user privacy, as these tokens frequently hold sensitive information like usernames and emails. Furthermore, if a backend salt server is responsible for user salt management, the JWT could potentially be exploited to retrieve your salt, which introduces additional risks.

However, a JWT leak does not mean loss of funds as long as the corresponding ephemeral private key is safe.

### User salt

The user salt is required to get access to the zkLogin wallet. This value is essential for both ZK proof generation and zkLogin address derivation.

The leak of user salt does not mean loss of funds, but it enables the attacker to associate your subject identifier (for example, `sub` ) with the Sui address . This can be problematic depending on whether pairwise or public subject identifiers are in use. In particular, there is no problem if pairwise IDs are used (for example, Facebook) as the subject identifier is unique for each RP. However, with public reusable IDs (for example, Google and Twitch), the globally unique `sub` value can be used to identify users.

### Ephemeral private key

The ephemeral private key's lifespan is tied to the maximum epoch specified in the nonce for creating a valid zero-knowledge proof. Should it be misplaced, a new ephemeral private key can be generated for transaction signing, accompanied by a freshly generated zero-knowledge proof using a new nonce. However, if the ephemeral private key is compromised, acquiring the user salt and the valid zero-knowledge proof would be necessary to move **Move** An open source programming language used for all activity on Sui. funds.

### Proof

Obtaining the proof itself cannot create a valid zkLogin transaction because an ephemeral signature over the transaction is also needed.

### Privacy

By default, there is no link between the OAuth subject identifier (for example, `sub` ) and a Su iaddress . This is the purpose of the user salt.

The JWT is not published onchain by default. The revealed values include `iss` , `aud` and `kid` so that the public input hash can be computed, any sensitive fields such as `sub` are used as private inputs when generating the proof.

The ZK proving service and the salt service (if maintained) can link the user identity because the user salt and JWT are known, but the 2 services are stateless by design.

## FAQ

#### What providers iszkLogin compatible with?

* zkLogin can support providers that work with OpenID Connect built on top of the OAuth 2.0 framework. This is a subset of OAuth 2.0 compatible providers. See latest table for all enabled providers. Other compatible providers are enabled through protocol upgrades in the future.

#### How is azkLogin Wallet different from a traditional private key wallet?

* Traditional private key wallets demand you to consistently recall mnemonics and passphrases, requiring secure storage to prevent private key compromise. On the other hand, a zkLogin wallet only requires an ephemeral private key storage with session expiry and the OAuth login flow with expiry. Forgetting an ephemeral key does not result in loss of funds, because you can always sign in again to generate a new ephemeral key and a new ZK proof.

#### How iszkLogin different from MPC ormultisig wallets?

* Multi-Party Computation (MPC) and multisig wallets rely on multiple keys or distributing multiple key shares and then defining a threshold value for accepting a signature. zkLogin does not split any individual private keys, but ephemeral private keys are registered using a fresh nonce when you authenticate with the OAuth provider. The primary advantage of zkLogin is that you do not need to manage any persistent private key anywhere, not even with any private keys management techniques like MPC or multisig . You can think of zkLogin as a 2FA scheme for an address , where the first part is your OAuth account and the second is your salt. Furthermore, because Sui natively supports multisig wallets, you can always include one or more zkLogin signers inside a multisig wallet for additional security, such as using the zkLogin part as 2FA in k-of-N settings.

#### If a OAuth account is compromised, what happens to thezkLoginaddress ?

* Because zkLogin is a 2FA system, an attacker that has compromised your OAuth account cannot access your zkLogin address unless they have separately compromised your salt.

#### If you lose access to my OAuth account, do you lose access to thezkLoginaddress ?

* Yes. You must be able to log into your OAuth account and produce a current JWT in order to use zkLogin .

#### Does losing an OAuth credential mean the loss of funds in thezkLogin wallet?

* A forgotten OAuth credential can typically be recovered by resetting the password in that provider. In the unfortunate event where your OAuth credentials are compromised, an adversary still requires the `user_salt` , but also learns which wallet is used in order to take over that account. Modern `user_salt` providers might have additional 2FA security measures in place to prevent provision of user salt even to entities that present a valid, non-expired JWT. It is also important to highlight that due to the fact that zkLogin addresses do not expose any information about the user identity or wallet used, targeted attacks by just monitoring the blockchain are more difficult. In the event where you lose access to your OAuth account permanently, access to that wallet is lost. If recovery from a lost OAuth account is desired, a good suggestion for wallet providers is to support the native Sui multisig functionality and add a backup method. It is even possible to have a multisig wallet that all signers are using zkLogin , such as a 1-of-2 multisig zkLogin wallet where the first part is Google and the second Facebook OAuth, respectively.

#### Can you convert or merge a traditional private key wallet into azkLogin one, or the reverse?

* No. The zkLogin wallet address is derived differently compared to a private key address .

#### Does myzkLoginaddress ever change?

* zkLogin address is derived from `sub` , `iss` , `aud` , and `user_salt` . The address does not change if you log in to the same wallet with the same OAuth provider, because `sub` , `iss` , `aud` , and `user_salt` remain unchanged in the JWT, even though the JWT itself might look different every time you log in. However, if you log in with different OAuth providers, your address changes because the `iss` and `aud` are defined distinctly per provider. In addition, each wallet or application maintains its own `user_salt` , so logging with the same provider for different wallets might also result in different addresses .

#### Can you have multipleaddresses with the same OAuth provider?

* Yes, this is possible by using a different wallet provider or different `user_salt` for each account. This is useful for separating funds between different accounts.

#### Is azkLogin Wallet custodial?

* A zkLogin wallet is a non-custodial or unhosted wallet. A custodial or hosted wallet is where a third party (the custodian) controls the private keys on behalf of a wallet user. No such third party exists for zkLogin wallets. Instead, a zkLogin wallet can be viewed as a 2-of-2 multisig where the 2 credentials are your OAuth credentials and the salt. Neither the OAuth provider, the wallet vendor, the ZK proving service, or the salt service provider is a custodian.

#### Generating a zero-knowledge proof is expensive, is a new proof required to be generated for everytransaction ?

* No. Proof generation is only required when ephemeral key pair expires. Since the nonce is defined by the ephemeral public key ( `eph_pk` ) and expiry ( `max_epoch` ), the zero-knowledge proof is valid until what the expiry is committed to the nonce in the JWT. The zero-knowledge proof can be cached and the same ephemeral key can be used to sign transactions until it expires.

#### DoeszkLogin work on mobile?

* zkLogin is a Sui native primitive and not a feature of a particular application or wallet. It can be used by any Sui developer, including on mobile.

#### Is account recovery possible if you lose the OAuth credentials?

* Yes, you can follow the OAuth providers recovery flow. The ephemeral private key can be refreshed and after completing a new OAuth login flow, you can obtain new zero-knowledge proof and sign transactions with the refreshed key.

#### What are some assumptions for thezkLogin circuit?

* Due to the way Groth16 works, Sui imposes length restrictions on several fields in the JWT. Some of the fields that are length-restricted include `aud` , `iss` , the JWT header, and the payload. For example, zkLogin can currently only work with `aud` values of up to length 120. In general, Sui tries to make sure that the restrictions are as generous as possible. Sui has decided on these values after looking at as many JWTs that could be obtained.

#### How iszkLogin different from other solutions that support social login?

* While providing social login with Web2 credentials for a Web3 wallet is not a new concept, the existing solutions have one or more of the trust assumptions:

1. Trust a different network or parties to verify Web2 credentials other than the blockchain itself, usually involving a JWK oracle posted onchain by a trusted party.
2. Trust some parties to manage a persistent private key, whether it uses MPC, threshold cryptography, or secure enclaves.
3. Rely on smart contracts to verify the JWT onchain with revealing privacy fields, or to verify zero-knowledge proofs onchain, which can be expensive.

* Some of the existing deployed solutions rely on some of these assumptions. [Web3Auth](https://web3auth.io/) and [Auth0 social login](https://auth0.com/learn/social-login) requires deployment of custom OAuth verifiers to [Web3auth Auth Network](https://github.com/web3auth) nodes to verify the JWT. [Magic Wallet](https://wallet.magic.link/) and [Privy](https://privy.io/) also require custom OAuth identity issuer and verifiers to adopt the [DID standard](https://www.w3.org/TR/did-1.0/) . All of the solutions still require persistent private key management, either with trusted parties like AWS through delegation, Shamir Secret Sharing, or MPC.

#### How to verify azkLogin signature offchain?

* The following options support a zkLogin signature over either transaction data or personal message using the JWK state on Sui and current epoch .

1. Use [Sui TypeScript SDK](https://sdk.mystenlabs.com/typescript) . This initializes a [GraphQL client](/references/sui-graphql) and calls the endpoint under the hood.
2. Use the GraphQL endpoint directly: `https://sui-[network].mystenlabs.com/graphql` , changing `[network]` to the appropriate value. See the [GraphQL documentation](/references/sui-api/sui-graphql/beta/reference/operations/queries/verify-zk-login-signature) for more details. This is recommended if you do not plan to run any servers or handle any JWK rotations.
3. Use the [Sui Keytool CLI](/references/cli/keytool) . This is recommended for debug usage.

```sh
$ sui keytool zk-login-sig-verify --sig $ZKLOGIN_SIG --bytes $BYTES --intent-scope 3 --network devnet --curr-epoch 3
```

4. Use a self-hosted server endpoint and call this endpoint, as described in `zklogin-verifier` . This provides logic flexibility.

#### Can I use zkLogin inside a multisig wallet?

* Yes. See the [Multisig guide](https://sdk.mystenlabs.com/typescript/cryptography/multisig#multisig-with-zklogin) for more details.

***

# Configure OpenID Providers

[URL](https://docs.sui.io/sui-stack/zklogin-integration/developer-account)

To integrate zkLogin. A Sui primitive that allows you to send transactions using an OAuth credential. with your app, you need an OAuth client from at least one of the available providers . You will use the Client ID and redirect URI from those providers in your zkLogin project. For example, the following TypeScript code constructs a Google login URL for testing.

```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 providers**

The following table lists the OpenID providers that can support zkLogin or are currently being reviewed to determine whether they can support zkLogin

| **Provider**   | **Can support?** | **Devnet** | **Testnet** | **Mainnet** |
| -------------- | ---------------- | ---------- | ----------- | ----------- |
| Facebook       | Yes              | Yes        | Yes         | Yes         |
| Google         | Yes              | Yes        | Yes         | Yes         |
| Twitch         | Yes              | Yes        | Yes         | Yes         |
| Apple          | Yes              | Yes        | Yes         | Yes         |
| Slack          | Yes              | Yes        | No          | No          |
| Kakao          | Yes              | Yes        | No          | No          |
| Microsoft      | Yes              | Yes        | No          | No          |
| AWS (Tenant)\* | Yes              | Yes        | Yes         | Yes         |
| Karrier One    | Yes              | Yes        | Yes         | Yes         |
| Credenza3      | Yes              | Yes        | Yes         | Yes         |
| RedBull        | Under review     | No         | No          | No          |
| Amazon         | Under review     | No         | No          | No          |
| WeChat         | Under review     | No         | No          | No          |
| Auth0          | Under review     | No         | No          | No          |
| Okta           | Under review     | No         | No          | No          |

* Sui supports AWS (Tenant) but the provider is enabled per tenant. Contact us for more information.

## Configuring an OpenID provider

Select a tab for instruction on configuring the client ID ( `$CLIENT_ID` in the previous example) and redirect URI ( `$REDIRECT_URI` in the previous example) with the relevant provider.

### Google

1. Navigate a browser to the [Google Cloud dashboard](https://console.cloud.google.com/projectselector2/home/dashboard) . Either sign in or register for a Google Cloud account.
2. Open **APIs & Services** > **Credentials** using the Google Cloud dashboard navigation.

   ![2](assets-2.webp)
3. On the Credentials page, select **CREATE CREDENTIALS** > **OAuth client ID**.

   ![2](assets-2.webp)
4. Set the **Application type** and **Name** of your application.

   ![3](assets-3.webp)
5. In the **Authorized redirect URIs** section, click the **ADD URI** button. Set the value for your redirect URI in the field. This should be the wallet or application frontend.

   ![4](assets-4.webp)
6. Click **Create**. If successful, Google Cloud displays the **OAuth client created** dialog with metadata, including your **Client ID**. Click **OK** to dismiss the dialog.

\
Your new OAuth client should now appear in the **OAuth 2.0 Client IDs** section of the Credentials page. Click the **Client ID** that appears next to the client to copy the value to your clipboard. Click the client name to access the redirect URI and other client data.

### Facebook

1. Register for a Facebook developer account and access the [dashboard](https://developers.facebook.com/apps/) .
2. Select **Build your app** then **Products** then **Facebook Login** where you can find the client ID. Set the redirect URL. This should be the wallet or application frontend.

   ![1](assets-5.webp)
3. Sign up for Facebook developer account

   ![2](assets-6.webp)
4. Go to Settings

### Twitch

1. Register for a Twitch developer account. Access the [dashboard](https://dev.twitch.tv/console) .
2. Go to **Register Your Application** then **Application** where you can find the client ID. Set the redirect URL. This should be the wallet or application frontend.

   ![1](assets-7.webp)
3. Sign up for Twitch developer account
4. Go to Console, Register for a Kakao developer account. Access the [dashboard](https://developers.kakao.com/console/app) and add an application.

   ![2](assets-8.webp)

***

# zkLogin Authentication

[URL](https://docs.sui.io/sui-stack/zklogin-integration/zklogin-demo)

This example demonstrates how to integrate [zkLogin](/sui-stack/zklogin-integration/zklogin) into a React application so users can authenticate with familiar OAuth providers (Google, Apple) and interact with the Sui blockchain without managing private keys.

## When to use this pattern

Use this pattern when you need to:

* Onboard users who do not have a Sui wallet by letting them sign in with Google or Apple.
* Derive a deterministic Sui address **Address** A unique, anonymous identity on a blockchain network. from a JWT token and a salt so users always get the same address .
* Generate and manage ephemeral key pairs that sign transactions on behalf of the authenticated user.
* Fetch a zero-knowledge proof from a prover service and attach it to a transaction **Transaction** A number of commands that execute on inputs to define the result of the transaction. as a zkLogin **zkLogin** A Sui primitive that allows you to send transactions using an OAuth credential. signature.
* Build a complete zkLogin flow in a React frontend without any backend server.

## What you learn

This example teaches:

* **Ephemeral key pairs:** The zkLogin system uses short-lived Ed25519 key pairs that are valid for a configurable number of epochs. The key pair signs transactions, but a zkLogin signature wraps the result and proves the signer owns the OAuth identity without revealing it.
* **OAuth popup flow:** The application opens an OAuth provider in a popup window, extracts the `id_token` from the URL hash, and sends it to the parent window through `postMessage` . This keeps the main application state intact during authentication.
* **ZK proof generation:** The prover service takes the JWT, extended ephemeral public key, max epoch **Epoch** A period of time defined by the network. , randomness, and salt, and returns a zero-knowledge proof. This proof lets the network verify the transaction was authorized by the OAuth identity holder without seeing the JWT.
* **Address derivation:**`jwtToAddress(jwt, salt)` deterministically maps an OAuth identity to a Sui address . The same user with the same salt always gets the same address , enabling persistent wallet identity across sessions.
* **zkLogin transaction signing:** The ephemeral key pair signs the transaction bytes, then `getZkLoginSignature` combines the ephemeral signature with the ZK proof and address seed to produce a composite signature the network accepts.

## Architecture

The example has 4 actors: a React frontend, an OAuth provider, a ZK prover service, and the Sui network. There are noMove **Move** An open source programming language used for all activity on Sui. contracts; the application sends native SUI transfer **Transfer** Changing the owner of an asset. transactions. The React frontend orchestrates the entire flow. It generates an ephemeral key pair, redirects the user to the OAuth provider (Google or Apple) in a popup, receives the JWT, sends proof inputs to the ZK prover service hosted by Mysten Labs, and uses the resulting proof to sign and execute transactions on the Sui network through gRPC **gRPC API** Replaces JSON-RPC on full nodes for data access. .

The diagram below traces 1 full zkLogin flow from key pair generation to transaction execution.

![](assets-9.webp)

The following steps walk through the flow:

1. The user clicks **Generate Key Pair** . The frontend fetches the current epoch from the Sui network, creates an Ed25519 ephemeral key pair, generates randomness, and computes a nonce. The key pair is valid for the configured number of epochs past the current one.
2. The user clicks **Login with Google** . The frontend opens a popup to the OAuth provider with the nonce embedded in the authorization URL. After the user authenticates, the provider redirects to the app's callback URL with an `id_token` in the URL hash. The popup extracts the token and sends it to the parent window through `postMessage` .
3. The user clicks **Generate ZK Proof** . The frontend sends the JWT, extended ephemeral public key, max epoch , randomness (base64-encoded), and salt to the Mysten Labs prover service. The prover returns a zero-knowledge proof. The frontend then derives the Sui address from the JWT and salt using `jwtToAddress` .
4. The user enters a recipient address and SUI amount. The frontend builds a `splitCoins` + `transferObjects`transaction , signs it with the ephemeral key pair, combines the signature with the ZK proof into a zkLogin signature using `getZkLoginSignature` , and executes the transaction through gRPC .

### HowzkLogin works

The zkLogin system maps an OAuth identity (like a Google account) to a Sui address using zero-knowledge proofs. The key insight is that the user's Sui address is derived deterministically from 2 inputs: their OAuth `sub` claim (a unique user identifier from the provider) and an application-specific salt. The same user with the same salt always gets the same address .

To authorize a transaction , the user does not sign with a long-lived private key. Instead, the application generates a short-lived ephemeral key pair, embeds the key pair's nonce in the OAuth login request, and receives a JWT that binds the user's identity to that ephemeral key. A ZK prover service then generates a proof that the JWT is valid and corresponds to the claimed address , without revealing the JWT itself to the network. The ephemeral key signs the transaction bytes, and the ZK proof wraps the signature so the network can verify authorization without seeing the user's OAuth credentials.

For more details, see the [zkLogin documentation](/sui-stack/zklogin-integration/zklogin) .

## Prerequisites

* Prerequisites

* [Install the latest version of Sui](/getting-started/onboarding/sui-install) .

* [Configure the Sui client](/getting-started/onboarding/configure-sui-client) .

* [Create a Sui address](/getting-started/onboarding/get-address) .

* [Get SUI Testnet tokens](/getting-started/onboarding/get-coins) .

* Download and install an IDE. The following are recommended, as they offerMove extensions:

* [VSCode](https://code.visualstudio.com/) , corresponding [Move extension](https://marketplace.visualstudio.com/items?itemName=mysten.move)

* [Emacs](https://www.gnu.org/software/emacs/) , corresponding [Move extension](https://github.com/amnn/move-mode)

* [Vim](https://www.vim.org/download.php) , corresponding [Move extension](https://github.com/yanganto/move.vim)

* [Zed](https://zed.dev/) , corresponding [Move extension](https://github.com/Tzal3x/move-zed-extension)

Alternatively, you can use the [Move web IDE](https://www.playmove.dev/) , which does not require a download. It does not support all functions necessary for this guide, however.

* [Download and install Git](https://git-scm.com/downloads) .
* [Node.js](https://nodejs.org/) 18 or later
* [Rust toolchain](https://rustup.rs/) (for the relayer service)
* A Sui wallet ( [Slush Wallet](https://slush.app/) or another compatible wallet)
* A [Google OAuth client](https://console.cloud.google.com/) and its client ID. Your Google OAuth client must have `http://localhost:5173/` set as an authorized JavaScript origin and authorized redirect URI.

## Setup

Follow these steps to set up the example locally.

Step 1: Clone the repo

```bash
$ git clone -b solution https://github.com/MystenLabs/sui-move-bootcamp.git
$ cd sui-move-bootcamp/K2
```

Step 2: Install dependencies

```bash
$ pnpm install
$ pnpm install @mysten/utils
```

Step 3: Configure environment variables

```bash
$ cp .env.example .env
```

Edit `.env` with your values:

[.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
```

Replace `YOUR_GOOGLE_CLIENT_ID` with the OAuth client ID from your Google Cloud project. The `VITE_SALT` can be any large random number; it determines the derived Suiaddress . Use the same salt consistently to get the sameaddress .

## Run the example

Start the frontend:

```bash
$ pnpm dev
```

Open `http://localhost:5173` in a browser. The landing page explains what zkLogin is. Click **Get Started** to enter the 4-step wizard. Complete each step in order: generate a key pair, sign in with Google, generate the ZK proof, then send a test SUI transfer to verify the full flow.

## Key code highlights

The following snippets are the parts of the code worth reading carefully.

### Ephemeral key pair generation

The `useEphemeral` hook generates a short-lived Ed25519 key pair and computes the nonce that binds it to the OAuth session.

Loading�\
The hook creates an `Ed25519Keypair` , extracts its public key, generates cryptographic randomness, fetches the current epoch to compute `maxEpoch` , and derives the nonce using `generateNonce` . The nonce is embedded in the OAuth URL so the JWT is bound to this specific ephemeral key. After `maxEpoch` passes, the key pair can no longer authorize transactions.

### OAuth popup token extraction

The `useOauthPopup` hook runs in the OAuth callback popup window and sends the JWT back to the parent.

Loading�\
When the OAuth provider redirects back to the application, the popup detects it has a `window.opener` , parses the `id_token` from the URL hash fragment, posts it to the parent window with `postMessage` , and closes itself. The parent window listens for this message in the `useOauthLogin` hook.

### ZK proof fetching

The `useZkProof` hook prepares the proof payload and fetches it from the Mysten Labs prover service.

Loading�\
The payload includes the JWT, the extended ephemeral public key (which encodes the key type and bytes), the max epoch , base64-encoded randomness and salt, and the key claim name ( `sub` ). The prover verifies the JWT signature and returns a ZK proof that the frontend stores for transaction signing.

### Address derivation from JWT

The `useWallet` hook derives the deterministic Sui address from the JWT and salt.

Loading�\
`jwtToAddress` maps the JWT's `sub` claim and the salt to a Sui address . `genAddressSeed` produces the address seed needed later for the zkLogin signature. The same JWT `sub` + salt always produces the same address , so the user's wallet persists across sessions.

### Transaction building andzkLogin execution

The `suiWriteClient` builds a SUI transfer transaction and executes it with a zkLogin signature.

Loading�\
The function fetches available coins and the reference gas **Gas** The computational cost of execution and object storage for a transaction. price in parallel, selects the largest coin for gas , constructs a `splitCoins` + `transferObjects`transaction , and signs it with the ephemeral key pair. The `useLiveTransaction` hook then combines this signature with the ZK proof using `getZkLoginSignature` to produce the final zkLogin signature before calling `executeZkLoginTransaction` .

### ZK proof fetch utility

The `fetchZkProof` utility calls the Mysten Labs prover endpoint.

Loading�\
The function POSTs the proof payload to `https://prover-dev.mystenlabs.com/v1` . The error handling distinguishes network failures (which might indicate CORS issues) from prover errors. For production applications, consider proxying prover requests through a backend to avoid CORS restrictions.

## Common modifications

* **Add session persistence:** Store the ephemeral key pair, JWT, and ZK proof in `localStorage` so users do not need to re-authenticate on page reload. Check `maxEpoch` against the current epoch to determine if the session is still valid.
* **Support multiple OAuth providers:** The `getOauthUrl` utility already handles Google and Apple. Add cases for Facebook, Twitch, or other OpenID Connect providers by constructing the appropriate authorization URL with the nonce.
* **Add sponsored transactions Sponsored transaction When one address pays the gas fee for a transaction submitted by another address. :** Instead of requiring the zkLogin wallet to hold SUI for gas , integrate a gas station service that sponsors transactions. Build the transaction with a separate gas owner and have the sponsor co-sign.

## Troubleshooting

The following sections address common issues with this example.

### OAuth popup is blocked

**Symptom:** Clicking **Login with Google** does nothing, or the browser shows a popup-blocked notification.

**Cause:** The browser's popup blocker is preventing the OAuth window from opening.

**Fix:** Allow popups for `localhost` in your browser settings, or click the popup-blocked icon in the address bar to allow the popup. The `openOauthPopup` function throws an error if the popup fails to open.

### Google sign-in fails

**Symptom:** The Google OAuth popup closes immediately or never appears.

**Cause:** The `VITE_OAUTH_CLIENT_ID` is misconfigured, the redirect URI does not match `http://localhost:5173/` , or third-party cookies are blocked.

**Fix:** Verify the Google Client ID and authorized redirect URIs in Google Cloud Console. Try a different browser or disable cookie-blocking extensions.

### `id_token` not found in URL hash

**Symptom:** The OAuth popup opens and authenticates, but the parent window never receives the JWT token.

**Cause:** The OAuth redirect URI in the Google Cloud console does not match the application's URL, or the response type is not set to `id_token` .

**Fix:** Verify the authorized redirect URI in the Google Cloud console matches `http://localhost:5173` exactly. Ensure the OAuth URL includes `response_type=id_token` and `scope=openid` .

### ZK proof fetch fails with CORS error

**Symptom:** The browser console shows a CORS error when calling the prover service.

**Cause:** The `prover-dev.mystenlabs.com` endpoint might not allow requests from `localhost` or your domain.

**Fix:** Use a backend proxy to forward requests to the prover service, or run the application on a domain the prover allows. For development, a browser extension that disables CORS can work as a temporary workaround.

### Derivedaddress has no balance

**Symptom:** The wallet address is derived successfully but shows a 0 SUI balance.

**Cause:** The derived address has not received any SUI. The address is deterministic but starts with no funds.

**Fix:** Use the [Sui faucet](https://faucet.sui.io/) to request Devnet . Copy the address from the Step 3 panel and paste it into the faucet **Faucet** A service that provides free Testnet tokens. .

### Transaction fails with `InvalidSignature`

**Symptom:** The transaction is submitted but the network rejects it with a signature error.

**Cause:** The ephemeral key pair has expired (currentepoch exceeds `maxEpoch` ), or the nonce in the JWT does not match the ephemeral key used for signing.

**Fix:** Restart the flow from Step 1. Generate a new ephemeral key pair, re-authenticate to get a fresh JWT with the new nonce, and generate a new ZK proof. Increase `VITE_EPHEMERAL_KEY_DURATION_EPOCHS` if keys expire too quickly.