Keyless blockchain accounts on Aptos

 

tl;dr: What is a keyless blockchain account? Put simply, “Your blockchain account = Your Google account”. In other words, this keyless approach allows you to derive a blockchain account from any of your existing OpenID Connect (OIDC) account (e.g., Google, Apple), rather than from a traditional secret key or mnemonic. There are no long-term secret keys you need to manage. There is also no multi-party computation (MPC) system managing your account for you. As a result, the risk of account loss is (more or less), the risk of losing your Google account. Keyless is built using a Groth16 zero-knowledge proof to maintain privacy in both directions: prevent the blockchain from learning anything about your Google account & prevent Google from learning anything about your blockchain account and transaction activity.

One day, I hope to edit this into a full blog post but, until then, here’s a bunch of resources.

Drawings

Flow: Keyless on-chain verification

Depicts what the blockchain validators need to do to verify a keyless TXN submitted by a user, Alice.

ZK relation: Keyless authentication

The ZK relation needed for keyless:

Flow: End-to-end keyless transacting

Depicts the full keyless flow: the user generating an ESK and EPK, the user signing into the dapp with the EPK as the OIDC nonce, the dapp getting a JWT, exchanging it for a pepper, getting a ZKP from the prover service, the user signing a TXN with their ESK, the dapp sending the TXN containing the ZKP and ephemeral signature, and finally the blockchain verifying everything.

Flow: End-to-end keyless ZKless-transacting (currently, disabled)

In case of emergency (e.g., a serious soundness issue in the ZK circuit), keyless supports a ZKless mode that is not privacy preserving. This, of course, is currently disabled on Aptos mainnet.

We depicts this (simpler) ZKless flow: the user generating an ESK and EPK, the user signing into the dapp with the EPK as the OIDC nonce, the dapp getting a JWT, the user signing a TXN with their ESK, the dapp sending the TXN containing the ephemeral signature, and finally the blockchain verifying everything.

ZK relation: Oblivious pepper service

The ZK relation needed to implement an oblivious pepper service:

Flow: Fetching your pepper obliviously

We depict the flow for a dapp to fetch its user’s pepper obliviously from the pepper service, without leaking the user’s ID nor the application’s ID to the service.

Write-ups

  1. I wrote a high-level overview of how keyless accounts work on the Aptos blockchain
  2. I wrote an in-depth explanation of how keyless accounts work (and their many caveats) in the 61st Aptos Improvement Proposal.

Slides

Code

  • Keyless blockchain validator logic here
  • Keyless governance logic here
  • Keyless prover service here
  • Keyless ZK circuit circom code here
  • Keyless pepper service here
  • Keyless TypeScript SDK here

Educational (d)apps and code

  • Example: Sending a keyless TXN to the Aptos mainnet via the SDK here
  • Example: Simple Keyless dapp on Aptos here with guide here
  • Example: Federated keyless dapp on Aptos here
  • Example: End-to-end dapp with Keyless here with guide here

Deployed applications

  1. Aptos Connect
  2. Merkle Trade

Aptos Improvement Proposals (AIPs)

AIPs for auxiliary keyless services:

AIPs for recent extensions to keyless:

Tweets

A tweetstorm summarizing Aptos Keyless can be found below:

Presentations

zkSummit’11

In April 2024, I gave a 20-minute presentation at zkSummit11:

GKR bootcamp

In January 2025, I gave a 1 hour bootcamp on keyless accounts:

Miscellaneous

  • Tutorial: Aptos Keyless Auth, by Osikhena Oshomah
  • Code: AnonAdhar, by PSE, does RSA2048-SHA2-256 signature verification in circom within ~900K R1CS constraints

Technical reference

$ \def\poseidon{\mathsf{Poseidon}} % \def\addridc{\mathsf{addr\_idc}} \def\pepper{\mathsf{pepper}} % \def\maxaudval{\texttt{max_aud_val}} \def\maxuidval{\texttt{max_uid_val}} \def\maxuidkey{\texttt{max_uid_key}} \def\audval{\mathsf{jwt}[\text{"aud"}]} \def\uidkey{\mathsf{uid\_key}} \def\uidval{\mathsf{jwt}[\uidkey]} %\def\audval{\mathsf{aud\_val}} %\def\uidval{\mathsf{uid\_val}} $

The notation below will not be explicitly defined; just exercise intuition! e.g., $\maxaudval$ is clearly the maximum number of bytes in $\audval$.

Hashing the identity commitment (IDC) in the address

\begin{align} \addridc \bydef \poseidon^\F_4\left( \begin{array}{l} \pepper[0..30],\\
\poseidon^\mathbb{S}_{\maxaudval}(\audval),\\
\poseidon^\mathbb{S}_{\maxuidval}(\uidval),\\
\poseidon^\mathbb{S}_{\maxuidkey}(\uidkey)\\
\end{array} \right) \end{align}

Define $\poseidon^\mathbb{S}_\ell(s)$.