zkLogin
Presentation: https://www.youtube.com/watch?v=vVMS1HbDoEM
Last updated
Presentation: https://www.youtube.com/watch?v=vVMS1HbDoEM
Last updated
In the traditional Web3 environment, users must manage their mnemonics directly, which poses a challenge since losing or having them stolen makes recovery difficult. Additionally, when users switch devices, they must restore their accounts using their self-managed mnemonics. In contrast, introduces an OAuth authentication method leveraging ZK to overcome the limitations of conventional Web3 login mechanisms and enhance the user experience. Notably, operates in a similar manner.
is a protocol that grants applications permission to access a user's data on their behalf. For example, to obtain file access permissions for Google Drive with OAuth2, the following steps are taken:
The user attempts to access Google Drive files through an application (MyApp).
MyApp requests user authorization from Google.
The user logs in to Google and grants permission.
Google issues an Access Token to MyApp.
MyApp sends the Access Token to the Google Drive API to access the files.
On the other hand, is a protocol based on OAuth2 that verifies a user's identity. In other words, it determines, "Is this person really who they claim to be?" For instance, when logging in using a Google account, the following steps occur:
The user clicks the "Login with Google" button on MyApp.
MyApp sends a login request to Google.
The user completes the Google login process.
Google issues an ID Token to MyApp.
MyApp verifies the ID Token and processes the user's login.
Entities such as Google, Facebook, and GitHub that provide this protocol are called OIDC Providers. The issued ID Token is known as a JWT (JSON Web Token).
To run the following script, you need to install the required packages locally:
Each field in the JWT has the following meaning:
iss
(issuer): Represents the entity (Identity Provider, IdP) that issued the JWT. In this case, it indicates that Google issued the token.
sub
(subject): The unique ID of the authenticated user. Providers like Google, Twitch, and Slack use the same sub
across all aud
values. However, Apple, Facebook, and Microsoft generate a unique sub
for each aud
.
aud
(audience): Specifies the target service (client ID) for which this JWT is intended.
iat
(issued at): The timestamp (Unix timestamp in seconds) when the JWT was issued.
exp
(expiration): The expiration timestamp (Unix timestamp in seconds) after which the JWT becomes invalid.
nonce
: A random value used to prevent replay attacks by ensuring request and response integrity.
Then, the OIDC Provider signs the JSON data using the following method:
This verification process can be performed by a blockchain validator. Here, the nonce
can be determined by the audience, i.e., the wallet application, and we aim to leverage this mechanism to improve the existing login process.
To reiterate, our goal is to ensure that users do not need to remember anything. This means we need to replace the traditional method of signing transactions with a private key derived from a mnemonic and the way we generate an on-chain address from it. The new approach should be both efficient and secure, ensuring that the OIDC Provider does not introduce security risks.
The first approach is to embed the transaction inside the nonce
. By doing so, users can sign transactions via Google login (or any OIDC protocol) instead of generating a private key from a mnemonic. The user's on-chain address can then be derived as follows:
To address the issue from Method 1, we can design a ZK circuit that hides sub
while still ensuring its validity.
salt
to Address DerivationTODO(chokobole): add how to ensure that only the user can derive their salt.
Next, we modify the ZK circuit accordingly:
However, this approach presents two major challenges:
Performance Overhead: A new ZK proof must be generated for every transaction, and validators must verify these proofs, which adds computational cost.
Potential for Abuse: Since the OIDC Provider holds the signing private key for transactions, it could be exploited maliciously.
Flow Overview
This approach has several advantages:
Reduced Proof Overhead: A ZK proof only needs to be generated when updating the ephemeral key pair, reducing computation costs.
Enhanced Security: Users create the signing private key themselves, preventing abuse by the OIDC Provider.
nonce
Now, the OIDC Provider can no longer determine which transactions a given sub
has signed, ensuring stronger privacy protection.
Typically, JWTs are valid for one hour, which is too short in the blockchain domain and inconvenient due to its reliance on real-time expiration. Instead, we extend the validity based on block height rather than real-world time.
For example, if we want the key to be valid for 10 hours and the blockchain's block time is 10 minutes, we can set:
We then update the nonce
with:
Additionally, the validator must check that:
The updated ZK circuit is as follows:
iss
to Public InputThe ZK circuit is further modified as:
Additionally, the validator must check that:
Through these methods, we successfully achieve the initial goal of passwordless Web3 authentication while maintaining security and privacy.
This raises an important question: how can we implement zkLogin without a nonce
in the JWT?
One key issue arises with this approach.
When an OIDC Provider signs a JWT, it typically follows this process:
SHA-2 computation – 74%
JSON parsing, Poseidon hashing, Base64 encoding, and additional constraints – 11%
One novel aspect of zkLogin is JWT parsing, with two key optimizations:
Header Parsing Optimization:
Since JWT headers are public, they are decoded outside the circuit to reduce computational overhead.
Selective Payload Parsing:
Based on the following assumptions:
The OIDC Provider adheres to the JSON specification.
The required fields (e.g., sub
, iss
, aud
, nonce
) are located at the top level of the JSON structure.
All JSON values are either strings (sub
, iss
, aud
, nonce
) or booleans (email_verified
).
JSON keys do not contain escaped quotes (e.g., \"sub\"
is not allowed).
Example JSON:
JWT elements are 8-bit values.
BN254 scalar field has a width of 253 bits.
This allows us to pack 16 elements at once, reducing constraints.
In addition, ZK proof verification can be optimized through engineering techniques, such as caching frequently used proofs.
Beyond performance, there are security concerns as well. If there were bugs in circom or the ZK circuit, users’ funds could be at risk. By centralizing proof generation on a server, such risks can be mitigated.
However, this approach introduces several risks:
Liveness Issue: If the prover service goes down, users will be unable to send transactions.
Cost Issue: If the prover service generates a large number of proofs, it incurs significant computational costs.
Centralization Issue: Users must trust the prover service to be honest and reliable.
Take a look at this timestamped video to see zkLogin in action.
zkLogin significantly improves UX by leveraging OIDC, allowing users to avoid managing mnemonics and other sensitive information. This innovation is a step forward toward mass adoption of Web3.
However, there are trade-offs:
Web3 was initially created to move beyond Web2. Yet, zkLogin relies on Web2 services to enhance the Web3 experience, which feels somewhat contradictory.
Client-side proof generation is crucial for fully decentralized authentication, but due to performance constraints, zkLogin currently relies on server-side proof generation, which is not ideal.
Validators must verify numerous ZK proofs, which is why Groth16 was chosen—it requires only three pairing operations per proof. However, instead of optimizing proof generation, both zkLogin and Keyless Wallet optimize circuit design for performance. This leads to an inconvenience in development since each circuit update requires a new trusted setup, posing a DevEx challenge.
Despite these limitations, zkLogin represents a meaningful step toward improving Web3 usability, and with further optimizations, it could strike a better balance between decentralization, security, and user experience.
You can decode the JWT at , which will display the following structure:
where is the secret key of the OIDC Provider, represents the claims included in the , and is the signed token that contains both and the signature .
On the client side, the generated can be verified as follows:
For further details on why OIDC was chosen over alternatives such as Passkey, MPC, and HSM, interested readers can refer to .
We explore various approaches to achieve this goal. The following explanation is inspired by , as it provides a clear and intuitive understanding.
Refer to of the for the reason why aud
is included.
However, this approach has a drawback: the validator can infer the relationship between sub
and , potentially exposing user identity information.
Here, we use the notation from .
However, this approach still has a flaw: the OIDC Provider can infer the relationship between sub
and , which could compromise user privacy.
To further improve privacy, we introduce a when deriving the user’s on-chain address:
With this modification, unless the OIDC Provider knows the , it cannot link the user's sub
to their on-chain activity. The only requirement is ensuring that the user does not lose their . However, since our goal is for users to remember nothing, we need a mechanism that allows only the user to generate their securely. (This document does not cover how to achieve this—refer to or of the for details.)
Now, we introduce an ephemeral key pair . The transaction is signed using this ephemeral key pair, allowing users to regenerate their key pair via Google login or another OIDC provider if they ever lose it. This ensures that users do not need to remember anything.
Additionally, by sending the proof that includes the new ephemeral public key , the user updates their ephemeral key on-chain, ensuring the system recognizes the new key for future transactions.
Next, we embed into the nonce
and modify the ZK circuit accordingly:
Key Generation: The user generates an ephemeral key pair .
Proof Creation: The user constructs a ZK proof proving ownership of their identity.
Tx Submission: The user signs a transaction and signature using , producing .
Submission & Key Update: The user submits , to the validator. Upon verification, the validator may update the user's ephemeral public key () on-chain.
Subsequent Transactions: Until the ephemeral key expires, the user only needs to submit a new transaction and its signature to the validator, without regenerating a new proof.
However, one issue remains: the OIDC Provider can still infer which transactions were signed by a given sub
through the .
A simple solution, such as hashing before embedding it into nonce
, is insufficient because is public. An adversary could still brute-force the mapping between and sub
.
To prevent this, we introduce randomness and modify the nonce
by hashing both and :
Since (OIDC Provider's public key) changes periodically, we add iss
to the public input. The validator must then verify that indeed corresponds to the public key of iss
.
The following figure, extracted from the , provides an overview of the entire login process:
As mentioned in of the paper, unfortunately, according to the , the nonce
field is not mandatory.
To answer this, let's recall the role of nonce
. It was used to bind to the JWT. Thus, by modifying the circuit derived in Method 7, we can adapt zkLogin as follows:
Previously, we introduced randomness to prevent the link between sub
and through nonce
. However, in this case, since nonce
does not exist, there is no longer a need for .
In previous methods, even if an attacker obtained a valid , they could not forge a transaction signature.
However, in this case, since we do not commit via nonce
an attacker who obtains a valid could use it to generate a different ephemeral key pair and sign arbitrary transactions.
This introduces a new security vulnerability, which must be addressed before deploying this approach. This could be mitigated to an extent through client-side proof generation, but still poses the same risk if the client themselves leak their valid own . Thus, since there is no current true solution to this vulnerability, transaction signatures with nonce-less JWT are not in use commercially today.
Thus, must be implemented as follows:
However, since , accessing a specific value like actually requires .
As a result, the ZK circuit must represent RSA signatures, SHA-2 hashing, and Base64 decoding—all of which are not ZK-friendly. This was implemented using , and it introduces the following computational overhead (Reference: ):
RSA signature verification – 15% (Utilizes a trick from )
Currently, the Sui zkLogin codebase is private. However, the implementation of Aptos Keyless Wallet is publicly available at 🔗 .
The function operates as follows. Assume represents the above JSON structure. For simplicity, whitespace handling and boolean values are omitted from the explanation.
Extract the substring .
Example: If and , then becomes "sub":"123",
.
Ensure ends with either ,
or }
:
Verify that is :
:
Using column index , extract:
Key:
Value:
Ensure both and start and end with "
:
For a string of length , where , the value can be computed as follows:
For , the indexing operator is defined as:
For example, to check whether ends with ,
, we transform it into:
Similarly, extracting follows:
Checking whether starts with "
can be rewritten as:
Since computing requires multiplications, extracting requires constraints. However, we can significantly reduce constraints by leveraging field packing:
Even after adding boundary checks and unpacking logic, the constraint count drops from to , resulting in a major efficiency gain. (The paper doesn't explain how.)
In addition to verifying transaction signatures using , as commonly used in Sui and Aptos, validators must now also verify ZK proofs.
Although a proof is generated only once, it is verified multiple times by validators. Given that ZK verification must not be significantly slower than traditional EdDSA signature verification, was chosen as the proving scheme. Groth16 requires only three pairing operations for verification, making it computationally efficient. (There are additional computations using public inputs, but these are omitted for simplicity.)
One advantage of Groth16 is that it allows . However, if at least one proof is invalid, all proofs must be re-verified individually, leading to a worst-case scenario that could be exploited as a DDoS attack vector.
"This drastically reduces the time between when a user logs in with their keyless account to when that user is able to transact, from ~25 seconds to 3 seconds. In turn, this greatly improves the user experience of keyless accounts." —
According to AIP-75, generating a proof using on the client side takes approximately 25 seconds, which is unacceptable from a UX perspective. To address this, both Sui and Aptos have adopted a prover service to generate proofs on the server side.
Privacy Issue: The prover service gains access to , meaning it can infer the relationship between sub
and (similar to the issue in Method 1). Fortunately, the prover does not know , preventing transaction signature forgery.
The image below, taken from the , shows that Sui has a server-side proof generation time of around 3 seconds, which is similar to Aptos's proof generation time mentioned above:
Written by from