docs/content/guides/developer/cryptography/zklogin-integration/zklogin.mdx
zkLogin 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:
The key differentiators that zkLogin brings to Sui are:
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.
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 of stakes by the validators without trusting any source of authority.
Full privacy: Nothing is required to submit on-chain except the zero-knowledge proof and the ephemeral signature.
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.
This section describes relevant OpenID terminology defined in the OpenID specification and how they are used in zkLogin, along with definitions for protocol details.
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 entities zkLogin currently supports.
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.
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.
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 stake.
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 | 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.
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.
(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.
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 (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.
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).
user_salt: A value introduced to unlink the OAuth identifier with the on-chain address.
max_epoch: The epoch at which the JWT expires. This is u64 used in Sui.
kc_name: The key claim name, for example sub.
kc_value: The key claim value, for example 110463452167303000000.
hashBytesToField(str, maxLen): Hashes the ASCII string to a field element using the Poseidon hash.
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.
Salt backup service: This is a backend service responsible for returning a salt per unique user. See zkLogin Integration Guide for other strategies to maintain salt.
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 on-chain along with the ephemeral signature for a zkLogin transaction.
At a high level, the zkLogin protocol works as follows:
A JWT is a signed payload from OAuth providers that includes a user-defined field named nonce. zkLogin uses the OpenID Connect OAuth flow by defining the nonce as a public key and an expiry epoch.
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.
A transaction is submitted on-chain with the ephemeral signature and the zero-knowledge proof. Sui authorities execute the transaction after verifying the ephemeral signature and the proof.
Instead of deriving the Sui address 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 on-chain address).
A ceremony generates the CRS, which is used to produce the proving key in the proving service and the verifying key in Sui authorities.
An ephemeral key pair (eph_sk, eph_pk) is generated andeph_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.
user_salt based on iss, aud, and sub.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 the address is consistent with the key claim and user salt.
iss, aud, and sub.After Step 10, Sui authorities verify the ZK proof against the provider's JWKs (stored by consensus) and the ephemeral signature.
The address is computed on the following inputs:
The address flag: zk_login_flag = 0x05 for zkLogin address. This serves as a domain separator.
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).
kc_value_F = hashBytesToField(kc_value, maxKCValueLen): The value of the key claim mapped using hashBytesToField.
aud_F = hashBytesToField(aud, maxAudValueLen): The relying party (RP) identifier.
iss: The OpenID Provider (OP) identifier.
user_salt: A value introduced to unlink the OAuth identifier with the on-chain 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)).
To preserve privacy of the OAuth artifacts, a zero-knowledge proof of possession is provided. zkLogin employs the Groth16 zkSNARK 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 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. Since this phase is circuit-agnostic, the ceremony adopts the result of the existing community contributed perpetual powers of tau. 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.
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.
Since 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 while the Docker option utilizes Kobi's implementation. 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 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 on the circuit from zkSecurity were posted. The ceremony adopted challenge #0081 (resulting from 80 community contributions) from perpetual powers of tau in phase 1, which is circuit agnostic. The output of the Drand 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 and phase 2.
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 as part of the validator software (protocol version 25 in release 1.10.1) that is used to verify the zkLogin transaction on Sui.
The following sections walk through all zkLogin artifacts, their security assumptions, and the consequences of loss or exposure.
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.
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.
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 funds.
Obtaining the proof itself cannot create a valid zkLogin transaction because an ephemeral signature over the transaction is also needed.
By default, there is no link between the OAuth subject identifier (for example, sub) and a Sui address. This is the purpose of the user salt.
The JWT is not published on-chain 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 since the user salt and JWT are known, but the 2 services are stateless by design.
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.sub, iss, aud, and user_salt. The address does not change if you log in to the same wallet with the same OAuth provider, since 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.user_salt for each account. This is useful for separating funds between different accounts.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.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.Use Sui TypeScript SDK. This initializes a GraphQL client and calls the endpoint under the hood.
Use the GraphQL endpoint directly: https://sui-[network].mystenlabs.com/graphql, changing [network] to the appropriate value. See the <UnsafeLink href="/references/sui-api/sui-graphql/beta/reference/operations/queries/verify-zk-login-signature.mdx">GraphQL documentation</UnsafeLink> for more details. This is recommended if you do not plan to run any servers or handle any JWK rotations.
Use the Sui Keytool CLI. This is recommended for debug usage.
$ sui keytool zk-login-sig-verify --sig $ZKLOGIN_SIG --bytes $BYTES --intent-scope 3 --network devnet --curr-epoch 3
Use a self-hosted server endpoint and call this endpoint, as described in zklogin-verifier. This provides logic flexibility.