-
Notifications
You must be signed in to change notification settings - Fork 103
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[AIP-X] - Enable interoperability for Federated Keyless Accounts for …
…the same issuer (#526) * add aip * update * fix typos * add zk relation * add relation update * add double slashes * add red color * add diff * update * update * update * update * update * update * update * address comments * address feedback * add bold to zk relation * rename file * Update and rename aip-10X.md to aip-108.md --------- Co-authored-by: Frances Liu <frances.liu168@gmail.com>
- Loading branch information
1 parent
b4114ac
commit afdf3a9
Showing
1 changed file
with
208 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,208 @@ | ||
--- | ||
aip: 108 | ||
title: Enable interoperability for Federated Keyless Accounts for the same issuer (user-pool/tenant) | ||
author: Oliver He (oliver.he@aptoslabs.com) | ||
Status: Draft # | Last Call | Accepted | Final | Rejected> | ||
last-call-end-date (*optional): <mm/dd/yyyy the last date to leave feedbacks and reviews> | ||
type: <Standard (Core, Networking, Interface, Application, Framework) | Informational | Process> | ||
created: 11/08/2024 | ||
updated (*optional): <mm/dd/yyyy> | ||
requires (*optional): https://github.com/aptos-foundation/AIPs/blob/main/aips/aip-103.md | ||
--- | ||
|
||
# AIP-108 - Enable interoperability for Federated Keyless Accounts for the same issuer (user-pool/tenant) | ||
|
||
## Summary | ||
|
||
This AIP proposes enabling Aptos Federated Keyless to be interoperable with dApps from the same issuer (user-pool/tenant). | ||
|
||
For IAM providers like Auth0 and Cognito, JWT tokens are scoped to a user-pool/tenant via the `iss` field, and they are also scoped to a specific application via the `aud` field. This means that JWTs from the same issuer but with different `aud` values are from different applications and cannot be used to derive the same Aptos Federated Keyless Account even though they represent the same user identity within the same user-pool/tenant. | ||
|
||
Many customers of Auth0 and Cognito have applications with different branding within the same user-pool/tenant ecosystem. Thus it is natural for such customers to use different application identifiers for their applications for organizational purposes. This AIP will enable Aptos Federated Keyless Accounts to be interoperable across such applications. | ||
|
||
## Risks | ||
|
||
- The relaxation of the `aud` field will allow for broader interoperability across applications within the same user-pool/tenant. This allows for broader adoption of Aptos Federated Keyless Accounts in such user ecosystems. | ||
|
||
Risks | ||
- Developers need to not use `aud`-less accounts when they want their keyless accounts to scoped to their own application. This can be mitigated by the Aptos SDK default behavior so that developers must explicitly enable using `aud`-less accounts. | ||
- This introduces an additional proving path, one where the `aud` is not checked. It is important that such proofs are rejected by the validator during transaction submission if the sending account requires `aud` to be present, as encoded in the KeylessPublicKey inside the `IdCommitment`. Note that no changes to the validator authentication path are needed to support this, the work is done in the ZK relation in the circuit. Validators will verify proofs same as before. | ||
- As circuit changes are needed to support `aud`-less accounts, a new ceremony will be needed to generate the proving key and verification key. | ||
- We want such accounts to be limited to Federated Keyless Accounts, as constructing Keyless Accounts without aud checks is unsafe. This can be mitigated by the Aptos SDK disallowing `aud`-less accounts from being used as Keyless Accounts. The prover will also reject proof requests for Keyless providers (as of now Google and Apple). However, in a world where 3rd party provers are permitted, we cannot prevent developers from using `aud`-less accounts as Keyless Accounts, but developers would not have any incentive to construct such accounts for their users (these accounts would be accessable by any other dApp, regardless of trust). | ||
- The verification key will need an update, which will invalidate all existing proofs that are cached client side by dApps using Keyless. dApps will need to re-fetch a new proof to submit transactions with Keyless accounts. Additionally the prover will need to start proving with the new proving key right away after the update. The prover has already been updated to support the proving key rotations and the SDK also supports state checks to invalidate old proofs. | ||
|
||
|
||
#### The updated keyless ZK relation $$\mathcal{R}$$ | ||
|
||
```math | ||
\mathcal{R}\begin{pmatrix} | ||
\mathsf{pih};\\ | ||
\textbf{w} = [ | ||
\textbf{w}_\mathsf{pub} = ( | ||
\mathsf{epk}, | ||
\mathsf{addr\_idc}, | ||
\mathsf{exp\_date}, | ||
\mathsf{exp\_horizon}, | ||
\mathsf{iss\_val}, | ||
\mathsf{extra\_field}, | ||
\mathsf{header}, | ||
\mathsf{jwk}, | ||
\mathsf{override\_aud\_val} | ||
),\\ | ||
\textbf{w}_\mathsf{priv} = ( | ||
\mathsf{\textcolor{red}{skip\_aud\_check}}, | ||
\mathsf{aud\_key}, | ||
\mathsf{uid\_key}, | ||
\mathsf{uid\_val}, | ||
r, | ||
\sigma_\mathsf{oidc}, | ||
\mathsf{jwt}, | ||
\rho | ||
) | ||
] | ||
\end{pmatrix} | ||
``` | ||
|
||
The **ZK relation $\mathcal{R}$** simply **performs the privacy-sensitive part of the verification** from the [leaky mode](#warm-up-leaky-signatures-that-reveal-the-users-and-apps-identity) above: | ||
|
||
1. Verify that the public inputs hash $\mathsf{pih}$ is correctly derived by hashing the inputs in $\textbf{w}\_\mathsf{pub}$ with $H\_\mathsf{zk}$ (as explained above). | ||
2. Check the OIDC provider ID in the JWT: | ||
- Assert $\mathsf{iss\\_val}\stackrel{?}{=}\mathsf{jwt}[\texttt{"iss"}]$ | ||
3. If using `email`-based IDs, ensure the email has been verified: | ||
- If $\mathsf{uid\\_key}\stackrel{?}{=}\texttt{"email"}$, assert $\mathsf{jwt}[\texttt{"email\\_verified"}] \stackrel{?}{=} \texttt{"true"}$ | ||
4. Check the user’s ID in the JWT: | ||
- Assert $\mathsf{uid\\_val}\stackrel{?}{=}\mathsf{jwt}[\mathsf{uid\\_key}]$ | ||
5. Check the address IDC uses the correct values: | ||
- Assert $\mathsf{addr\\_idc} \stackrel{?}{=} H'(\mathsf{uid\\_key}, \mathsf{uid\\_val}, \mathsf{aud\\_val}; r)$ | ||
6. **If we are doing `aud` checks (i.e., $\mathsf{skip\\_aud\\_check} = \bot$)** | ||
- ***Then:* Are we in normal mode (i.e., we are not in recovery mode $\Leftrightarrow \mathsf{override\\_aud\\_val} = \bot$)** | ||
+ ***Then:* check the managing application’s ID in the JWT: assert $\mathsf{aud\\_val}\stackrel{?}{=}\mathsf{jwt}[\texttt{"aud"}]$** | ||
+ ***Else:* check that the recovery service’s ID is in the JWT: assert $\mathsf{override\\_aud\\_val}\stackrel{?}{=}\mathsf{jwt}[\texttt{"aud"}]$** | ||
- ***Else:* assert $\mathsf{aud\\_val}\stackrel{?}{=}\texttt{""}$ (i.e. $\mathsf{aud\\_val}$ should equal the empty string).** | ||
> *Old version:* | ||
> | ||
> Are we in normal mode (i.e., we are not in recovery mode $\Leftrightarrow \mathsf{override\\_aud\\_val} = \bot$) | ||
> + *Then:* check the managing application’s ID in the JWT: assert $\mathsf{aud\\_val}\stackrel{?}{=}\mathsf{jwt}[\texttt{"aud"}]$ | ||
> + *Else:* check that the recovery service’s ID is in the JWT: assert $\mathsf{override\\_aud\\_val}\stackrel{?}{=}\mathsf{jwt}[\texttt{"aud"}]$ | ||
7. Check the EPK is committed in the JWT’s `nonce` field: | ||
- Assert $\mathsf{jwt}[\texttt{"nonce"}] \stackrel{?}{=} H’(\mathsf{epk},\mathsf{exp\\_date};\rho)$ | ||
8. Check the EPK expiration date is not too far off into the future: | ||
- Assert $\mathsf{exp\\_date} < \mathsf{jwt}[\texttt{"iat"}] + \mathsf{exp\\_horizon}$ | ||
9. Parse $\mathsf{extra\\_field}$ as $\mathsf{extra\\_field\\_key}$ and $\mathsf{extra\\_field\\_val}$ and assert $\mathsf{extra\\_field\\_val}\stackrel{?}{=}\mathsf{jwt}[\mathsf{extra\\_field\\_key}]$ | ||
10. Verify the OIDC signature $\sigma_\mathsf{oidc}$ under $\mathsf{jwk}$ over the JWT $\mathsf{header}$ and payload $\mathsf{jwt}$. | ||
|
||
## Alternative solutions | ||
|
||
The alternative is to add an additional keyless public key type where the formula to compute the `IdCommitment` does not contain the `aud` at all. | ||
|
||
This is the advantage of explicit type safety as a completely new validation path would be implemented. There would be no risk of such proofs being accepted for accounts that require `aud` to be present due to explicit differences in how the proof would be gated on the type of public key. | ||
|
||
However the drawbacks include: | ||
- We need to add a new keyless public key type, which may not be needed if we can leverage the existing design. And avoiding proliferation of keyless public key types is desirable. | ||
- Requiring implementation of a new authentication path in the authenticator, which may be error prone and takes additional engineering effort. | ||
- Requires more complex changes to the prover as it would need to support a different public inputs hash calculation in order to differentiate between accounts with and without `aud`. Or it would need to use a different circuit version entirely and there would be a need to maintain two different circuit versions at the same time. | ||
|
||
Thus if we can leverage the existing design, it would be preferable to do so. | ||
|
||
## Specification and Implementation Details | ||
|
||
This AIP's implementation has three parts - | ||
|
||
1. We add an additional private input, `skip_aud_check`, into the circuit. This value will indicate whether the `aud` check is enabled. | ||
- If it is disabled, the circuit will do the status quo set of verifications. | ||
- If it is enabled, the circuit will use an empty string for the `aud` private input (as provided by the prover), which will be used as the `aud` value committed in the IdCommitment. The circuit will skip matching the value of the JWT's aud claim with the `aud` private input (which is the empty string). | ||
|
||
Since the `aud` value is committed to in the `IdCommitment`, a proof generated with `skip_aud_check` will be rejected by the validator if the account is not `aud`-less, as the `IdCommitment` will be different, resulting in proof verification failing due to the public inputs hash computed by the validator (which includes the `IdCommitment`) not being able to satisfy the ZK relation in order to verify the proof. | ||
|
||
Similarly, a proof generated without `skip_aud_check` will be rejected by the validator if the account *is* `aud`-less, since the ZK relation will not be able to match the empty `aud` in the `IdCommitment` with the non-empty `aud` in the JWT. | ||
|
||
2. The prover API will also require an update to allow for indiciating whether the `aud` check is enabled. This will be done by adding a new boolean argument `skip_aud_check` to the `prove` API. | ||
|
||
```rust | ||
#[derive(Debug, Serialize, Deserialize)] | ||
pub struct RequestInput { | ||
pub jwt_b64: String, | ||
pub epk: EphemeralPublicKey, | ||
#[serde(with = "hex")] | ||
pub epk_blinder: EphemeralPublicKeyBlinder, | ||
pub exp_date_secs: u64, | ||
pub exp_horizon_secs: u64, | ||
pub pepper: Pepper, | ||
pub uid_key: String, | ||
pub extra_field: Option<String>, | ||
pub aud_override: Option<String>, | ||
pub skip_aud_check: bool, // New argument | ||
} | ||
``` | ||
|
||
3. The pepper API will also require an update to allow for indicating whether the `aud` check is enabled. This is because the pepper is derived using the `aud` value as one of the inputs, and for audless accounts the `aud` value needs to be the empty string in order for the account to be used across applications (which will differ by the value of the `aud` claim in the JWT). Thus the pepper API needs to know whether the account is `aud`-less to construct the pepper appropriately. This will be done by adding a new boolean argument `skip_aud_check` to the `fetch_pepper` API. | ||
|
||
```rust | ||
#[derive(Debug, Deserialize, Serialize)] | ||
pub struct PepperRequest { | ||
pub jwt: String, | ||
pub epk: EphemeralPublicKey, | ||
pub exp_date_secs: u64, | ||
pub epk_blinder: Vec<u8>, | ||
pub uid_key: Option<String>, | ||
pub derivation_path: Option<String>, | ||
pub skip_aud_check: bool, // New argument | ||
} | ||
``` | ||
Additionally, the pepper service will limit `skip_aud_check` to only be true for OIDC providers that support `aud`-less accounts (currently only Auth0 and Cognito). | ||
|
||
4. The SDK will also need to be updated to support instantiating of `aud`-less accounts. This will require adding a new boolean argument to the `KeylessAccount` constructor and constructing the `KeylessPublicKey` and `AccountAddress` appropriately. | ||
|
||
## Testing (Optional) | ||
|
||
1. Write unit tests for the circuit to verify that it correctly handles the `aud` check. | ||
2. Write unit tests for the SDK to verify that it correctly instantiates accounts with and without `aud` checks. | ||
3. Do a manual end-to-end test in devnet/testnet via the SDK once the verification key is updated. | ||
4. Write smoke tests ensuring that `aud`-less accounts are rejected if the account requires `aud` to be present. | ||
5. Make sure pepper service and prover service only allows `skip_aud_check` = true for OIDC providers that support `aud`-less accounts. | ||
|
||
## Security Considerations | ||
|
||
The core security considerations are: | ||
- Making sure the the circuit can securely support `aud`-less accounts. | ||
- Making sure that such proofs are rejected if the account requires `aud` to be present (as encoded in the `KeylessPublicKey`s `IdCommitment`). | ||
|
||
## Future Potential | ||
|
||
This will allow onboarding more users into the Aptos blockchain via keyless accounts[^aip-61] and its extensions. | ||
|
||
## Timeline | ||
|
||
-Circuit changes: End of October 2024. | ||
-Ceremony completion: End of November 2024. | ||
-SDK update: by ceremony completion. | ||
-Prover service update: by ceremony completion. | ||
-Devnet verification key update: After ceremony completion. | ||
-Devnet testing: After verification key update. Should take a few hours. | ||
-Testnet verification key update: After devnet testing. | ||
-Testnet testing: After testnet verification key update. | ||
-Mainnet verification key update proposal: End of November 2024. | ||
-Mainnet verification key update: A week after proposal submission. Estimated early December 2024. | ||
|
||
### Suggested implementation timeline | ||
|
||
See above. | ||
|
||
### Suggested developer platform support timeline | ||
|
||
Already supported via telegram. | ||
|
||
### Suggested deployment timeline | ||
|
||
See above. | ||
|
||
## References | ||
|
||
[^aip-61]: https://github.com/aptos-foundation/AIPs/blob/main/aips/aip-61.md | ||
[^aip-67]: https://github.com/aptos-foundation/AIPs/blob/main/aips/aip-67.md | ||
[^aip-75]: https://github.com/aptos-foundation/AIPs/blob/main/aips/aip-75.md | ||
[^aip-81]: https://github.com/aptos-foundation/AIPs/blob/main/aips/aip-81.md | ||
[^aip-61-recovery]: https://github.com/aptos-foundation/AIPs/blob/main/aips/aip-61.md#recovery-service | ||
[^jwks]: https://appleid.apple.com/.well-known/openid-configuration | ||
[^passkeys]: https://github.com/aptos-foundation/AIPs/blob/main/aips/aip-66.md | ||
[^ppid]: https://openid.net/specs/openid-connect-core-1_0.html#Terminology |