aip | title | author | discussions-to (*optional) | Status | last-call-end-date (*optional) | type | created | updated (*optional) | requires (*optional) |
---|---|---|---|---|---|---|---|---|---|
81 |
Pepper service for keyless accounts |
Zhoujun Ma (zhoujun@aptoslabs.com) |
<a url pointing to the official discussion thread> |
Draft |
<mm/dd/yyyy the last date to leave feedbacks and reviews> |
<Standard (Core, Networking, Interface, Application, Framework) | Informational | Process> |
03/17/2024 |
<mm/dd/yyyy> |
<AIP number(s)> |
In keyless accounts an end user needs a private blinding factor (pepper) as an input in the privacy-preserving account address derivation: as long as the pepper is not leaked, the link between the account and the provider/dApp owner behind it remains hidden.
This AIP proposes a solution to manange pepper for the end users without actually storing them by deploying a public service (operated by Aptos Labs) that computes the pepper as a verifiable unpredictable function (VUF) of some session data (namely, the ephemeral public key from the end user and the authorization token (the JWT) from the OIDC provider).
This is a fundamental building block keyless accounts infrastructure.
This is a fundamental building block keyless accounts infrastructure.
DApp/SDK developers need to implement the interaction with pepper service in order to support the keyless flow.
The whole point of using keyless accounts is to save users from memorizing extra secrets, and this breaks the point.
It would make keyless account dApp-dependent: if the dependency dApp dies, all the dependent accounts are not accessible.
A few data items involved in the pepper service interaction is briefly explained below. For the full glossaries, see keyless account specification.
A pair of ephemeral private key (ESK) and ephemeral public key (EPK) is generated each time the user logs in to the OIDC provider in order to use a keyless flow-enabled app. A keyless transaction requires an ephemeral signature generated using the ESK.
An EPK expiry date is a timestamp specified for every EPK. If an EPK's expiry date is past, the EPK is considered expired and cannot be used to sign transactions.
EPK blinder are some random bytes used to blind EPK and its expiry date
when committing them into the nonce
field of the sign request sent to the OIDC provider.
(The issued JWT
will contained the same nonce
.)
A function derive_nonce()
is required to commit user's EPK, EPK blinder and the expiry timestamp into a nonce.
The nonce is then sent to the OIDC provider and signed as part of JWT payload.
The same function needs to be implemented as part of the ZK relation, so SNARK-friendly hash function should be preferred. The reference implementation uses Poseidon hash function over BN254.
Roughly speaking, a VUF is a mapping f(x)
implicitly determined by a key pair sk, pk
such that:,
- for a random input
x
, it is hard to predictf(x)
without private keysk
; - for an output
y
claimed to bef(x)
, anyone can verify if the claim is true using the public keypk
.
These properties make the VUF output a good candidate for the pepper used in the keyless account flow.
Some concrete VUF schemes applicable here:
- BLS VUF (over BLS12-381 G1 group, refered to as
Bls12381G1Bls
below). - Pinkas VUF.
Below we refer to the VUF scheme being used in pepper calculation as vuf
,
and the evaluation function as vuf.eval
.
In addition, an asymmetric encryption with variable-length input is required to encrypt the pepper with end user's ESK, to ensure only the end user can see the pepper. Otherwise, anyone who has access to a leaked JWT can learn the account address by interacting with the pepper service.
Here is an example asymmetric encryption scheme that combines ElGamal asymmetric encryption scheme and AES-256-GCM (Aes256Gcm.enc, Aes256Gcm.dec)
symmetric encryption with variable-length input (assuming the ephemeral key pair is an Ed25519 key pair, scheme referred to as ElGamalCurve25519Aes256Gcm
below).
- Let
x, Y
be the Ed25519 key pair. - To ecrypt variable-lenth input
ptxt
:- Pick a point
M
from the Ed25519's underlying prime-order group uniformly at random. - Encrypt
M
using El-Gamal with public keyY
. Denote byC0, C1
the ciphertext, whereC0, C1
are 2 points on Curve25519. - Hash
M
to an AES-256-GCM keyaes_key
. - Encrypt
ptxt
using AES-256-GCM with encryption keyaes_key
. Denote byctxt
the variable-length AES ciphertext. - The full ciphertext is
C0, C1, ctxt
.
- Pick a point
- To decrypt
C0, C1, ctxt
:- Recover
M
fromC0, C1
using El-Gamal decryption and private keyx
. - Hash
M
to an AES-256-GCM keyaes_key
. - Decrypt
ptxt
fromctxt
using AES-256-GCM decryption withaes_key
. - The final output is
ptxt
.
- Recover
Below we refer to the asymmetric encryption scheme being used as asymmetric
,
and its encryption algorithm as asymmetric.enc
.
The ephemeral key pair scheme, along with the parameters derive_nonce
, vuf
, asymmetric
should be chosen before deploying pepper.
Additionally, depending on the choice vuf
, a VUF key pair should also be generated and stored securely for the pepper service to read.
The pepper service should publish its VUF public key by opening an endpoint for anyone to fetch its public key.
Here is an example response where the VUF public key is serialized, hexlified and wrapped in a JSON,
assuming vuf=Bls12381G1Bls
.
{
"public_key": "b601ec185c62da8f5c0402d4d4f987b63b06972c11f6f6f9d68464bda32fa502a5eac0adeda29917b6f8fa9bbe0f498209dcdb48d6a066c1f599c0502c5b4c24d4b057c758549e3e8a89ad861a82a789886d69876e6c6341f115c9ecc381eefd"
}
The pepper repquest from user to pepper service should be as follows. See the comments in the examples for detailed description.
{
/// User's ephemeral public key (EPK), serialized and hexlified.
"epk": "002020fdbac9b10b7587bba7b5bc163bce69e796d71e4ed44c10fcb4488689f7a144",
/// The EPK expiry date, represented in seconds seince unix epoch.
"exp_date_secs": 1710800689,
/// The EPK blinding factor, hexlified.
"epk_blinder": "00000000000000000000000000000000000000000000000000000000000000",
/// A JWT issued by the provider for the end user logging in to the dApp with the specified EPK.
"jwt_b64": "xxxx.yyyy.zzzz",
/// Optional. Determines which JWT claim to read user ID from. Defaults to `"sub"`.
"uid_key": "email",
/// Optional. If the `aud` claim in the JWT represents a special account recovery app, and `aud_override` is specified in the request,
/// the pepper service should compute pepper for the `aud_override` instead of the original `aud` in JWT.
///
/// This helps end users to transact with the account in case
/// the OIDC provider blocked the app and stopped generating JWTs for it.
"aud_override": "some-client-id-of-some-banned-google-app",
}
The pepper request should be rejected if any check below fails.
epk
should be valid.- Decoding
jwt_b64
into JSONsjwt.header
,jwt.payload
,jwt.sig
should succeed, andjwt.payload
should contain the following claims.jwt.payload.nonce
: a customizable field that keyless account scheme use to put commitment of user's ephemeral public key.jwt.payload.sub
: the user ID issued by the provider (or whatever field specified byuid_key
in the request)jwt.payload.iss
: the providerjwt.payload.aud
: the client ID of the app issued by the providerjwt.payload.iat
: JWT generation time
- Ensure
epk
is committed injwt.nonce
, i.e.,jwt.nonce == derive_nonce(epk_blinder, exp_date_secs, epk)
. jwt.payload.iss
andjwt.header
should identiy a currently active JWT verification key (a.k.a JWK).- Verification of
jwt.sig
withjwt.payload
and the referenced JWK should pass. exp_date_secs
should neither be in the past, nor too far in the future beyondjwt.iat
plus a configured duration (default: 10,000,000 secs or ~115.74 days).
- Let
uid_val
be the user ID fromjwt.payload
indicated byuid_key
. - Let
aud
be the effective client ID determined byjwt.payload.aud
andaud_override
. - Let
input
be a canonical serialization of data itemsjwt.payload.iss, uid_key, uid_val, aud
. - Compute
pepper
asvuf.eval(vuf_sk, input)
. - Let
pepper_bytes
be a serialization of the VUF outputpepper
. - Compute
pepper_encryped
asasymmetric.enc(epk, pepper_bytes)
.
The pepper response from the pepper service to the user should simply be the pepper encrypted by the EPK, hexlified, and wrapped in a JSON.
Below is an example pepper response, assuming vuf=Bls12381G1Bls
and asymmetric=ElGamalCurve25519Aes256Gcm
.
{
"signature_encrypted": "6e0cf0dfdffbd22d0108195b54949b7840c3b7e4c9168f0899b60950f16e52cd54f0306cb8e76eda9fb3d5be6890cd3fc2c0cb259e3578dab6c7496b6d64553d4463a18aecf5bc3fc629cd88f9a78221b05b4d04d1a0a20292ed11f4197c5169227c86a6775b1c2990709cadf010cbf624763d68783eb466892d69a70c3c95a9fdffe5917e4554871db915b0"
}
https://github.com/aptos-labs/aptos-core/tree/main/keyless/pepper
Testing should be done as part of end-to-end testing of keyless accounts.
The proposed pepper service is centralized, making it a single point of failure in the keyless transaction flow. This can be mitigated by multiple deployments for now, which is easy to maintain due to its stateless nature. In the future, a decentralized pepper service potentially run as part of Aptos validators can fully resolve the issue.
If the VUF private key is lost, all keyless accounts are inaccessible (unless the pepper is locally cached on user end).
If the VUF private key is leaked, the link between a keyless account and the OIDC provider/dApp owner is no longer private. For example, Google now may know which keyless account is derived from the JWT it signed.
If the pepper service is instantiated with a weighted VUF (e.g., Pinkas VUF), it has the potential to be decentralized:
- VUF private key needs to be shared between Aptos validators using a thresholded weighted key sharing scheme.
- A pepper request will be broadcasted to all validators and the pepper is obtained by verifying and aggregating enough pepper responses from validators.
2024.01-03
SDK support are implemented in lock step with pepper service development.
Release 1.10
See risks and drawbacks.