keyless_account - [devnet]
This module is responsible for configuring keyless blockchain accounts which were introduced in AIP-61.
use 0x1::bn254_algebra;use 0x1::chain_status;use 0x1::config_buffer;use 0x1::crypto_algebra;use 0x1::ed25519;use 0x1::option;use 0x1::signer;use 0x1::string;use 0x1::system_addresses;Constants
A serialized BN254 G1 point is invalid.
const E_INVALID_BN254_G1_SERIALIZATION: u64 = 2;A serialized BN254 G2 point is invalid.
const E_INVALID_BN254_G2_SERIALIZATION: u64 = 3;The training wheels PK needs to be 32 bytes long.
const E_TRAINING_WHEELS_PK_WRONG_SIZE: u64 = 1;Structs
Group
#[resource_group(#[scope = global])]struct GroupFields
-
dummy_field: bool
Resources
Groth16VerificationKey
The 288-byte Groth16 verification key (VK) for the ZK relation that implements keyless accounts
#[resource_group_member(#[group = 0x1::keyless_account::Group])]struct Groth16VerificationKey has drop, store, keyFields
-
alpha_g1: vector<u8> -
32-byte serialization of
alpha * G, whereGis the generator ofG1. -
beta_g2: vector<u8> -
64-byte serialization of
alpha * H, whereHis the generator ofG2. -
gamma_g2: vector<u8> -
64-byte serialization of
gamma * H, whereHis the generator ofG2. -
delta_g2: vector<u8> -
64-byte serialization of
delta * H, whereHis the generator ofG2. -
gamma_abc_g1: vector<vector<u8>> -
\forall i \in {0, ..., \ell}, 64-byte serialization of gamma^{-1} * (beta * a_i + alpha * b_i + c_i) * H, whereHis the generator ofG1and\ellis 1 for the ZK relation.
Configuration
#[resource_group_member(#[group = 0x1::keyless_account::Group])]struct Configuration has copy, drop, store, keyFields
-
override_aud_vals: vector<string::String> -
An override
audfor the identity of a recovery service, which will help users recover their keyless accounts associated with dapps or wallets that have disappeared. IMPORTANT: This recovery service **cannot**, on its own, take over user accounts: a user must first sign in via OAuth in the recovery service in order to allow it to rotate any of that user's keyless accounts.Furthermore, the ZKP eventually expires, so there is a limited window within which a malicious recovery service could rotate accounts. In the future, we can make this window arbitrarily small by further lowering the maximum expiration horizon for ZKPs used for recovery, instead of relying on the
max_exp_horizon_secsvalue in this resource.If changed: There is no prover service support yet for recovery mode => ZKPs with override aud’s enabled will not be served by the prover service => as long as training wheels are “on,” such recovery ZKPs will never arrive on chain. (Once support is implemented in the prover service, in an abundance of caution, the training wheel check should only pass if the override aud in the public statement matches one in this list. Therefore, changes to this value should be picked up automatically by the prover service.)
-
max_signatures_per_txn: u16 -
No transaction can have more than this many keyless signatures.
If changed: Only affects the Aptos validators; prover service not impacted.
-
max_exp_horizon_secs: u64 -
How far in the future from the JWT's issued-at-time can the EPK expiration date be set?
Specifically, validators enforce that the ZKP's expiration horizon is less than this
max_exp_horizon_secsvalue.If changed: Only affects the Aptos validators; prover service not impacted.
-
training_wheels_pubkey: option::Option<vector<u8>> -
The training wheels PK, if training wheels are on.
If changed: Prover service has to be re-deployed with the associated training wheel SK.
-
max_commited_epk_bytes: u16 -
The max length of an ephemeral public key supported in our circuit (93 bytes)
Note: Currently, the circuit derives the JWT’s nonce field by hashing the EPK as:
Poseidon_6(epk_0, epk_1, epk_2,max_commited_epk_bytes,exp_date,epk_blinder)and the public inputs hash by hashing the EPK with other inputs as:
Poseidon_14(epk_0, epk_1, epk_2,max_commited_epk_bytes,[...])where
max_committed_epk_byteis passed in as one of the witnesses to the circuit. As a result, (some) changes to this field could technically be handled by the same circuit: e.g., if we let the epk_i chunks exceed 31 bytes, but no more than 32, thenmax_commited_epk_bytescould now be in (93, 96]. Whether such a restricted set of changes is useful remains unclear. Therefore, the verdict will be that…If changed: (Likely) requires a circuit change because over-decreasing (or increasing) it leads to fewer (or more) EPK chunks. This would break the current way the circuit hashes the nonce and the public inputs. => prover service redeployment.
-
max_iss_val_bytes: u16 -
The max length of the value of the JWT's
issfield supported in our circuit (e.g.,"https://accounts.google.com")If changed: Requires a circuit change because the
issfield value is hashed inside the circuit asHashBytesToFieldWithLen(MAX_ISS_VALUE_LEN)(iss_value, iss_value_len)whereMAX_ISS_VALUE_LENis a circuit constant hard-coded tomax_iss_val_bytes(i.e., to 120) => prover service redeployment.. -
max_extra_field_bytes: u16 -
The max length of the JWT field name and value (e.g.,
"max_age":"18") supported in our circuitIf changed: Requires a circuit change because the extra field key-value pair is hashed inside the circuit as
HashBytesToFieldWithLen(MAX_EXTRA_FIELD_KV_PAIR_LEN)(extra_field, extra_field_len)whereMAX_EXTRA_FIELD_KV_PAIR_LENis a circuit constant hard-coded tomax_extra_field_bytes(i.e., to 350) => prover service redeployment. -
max_jwt_header_b64_bytes: u32 -
The max length of the base64url-encoded JWT header in bytes supported in our circuit.
If changed: Requires a circuit change because the JWT header is hashed inside the circuit as
HashBytesToFieldWithLen(MAX_B64U_JWT_HEADER_W_DOT_LEN)(b64u_jwt_header_w_dot, b64u_jwt_header_w_dot_len)whereMAX_B64U_JWT_HEADER_W_DOT_LENis a circuit constant hard-coded tomax_jwt_header_b64_bytes(i.e., to 350) => prover service redeployment.
Functions
new_groth16_verification_key
public fun new_groth16_verification_key(alpha_g1: vector<u8>, beta_g2: vector<u8>, gamma_g2: vector<u8>, delta_g2: vector<u8>, gamma_abc_g1: vector<vector<u8>>): keyless_account::Groth16VerificationKeyImplementation
public fun new_groth16_verification_key(alpha_g1: vector<u8>, beta_g2: vector<u8>, gamma_g2: vector<u8>, delta_g2: vector<u8>, gamma_abc_g1: vector<vector<u8>>): Groth16VerificationKey { Groth16VerificationKey { alpha_g1, beta_g2, gamma_g2, delta_g2, gamma_abc_g1, }}new_configuration
public fun new_configuration(override_aud_val: vector<string::String>, max_signatures_per_txn: u16, max_exp_horizon_secs: u64, training_wheels_pubkey: option::Option<vector<u8>>, max_commited_epk_bytes: u16, max_iss_val_bytes: u16, max_extra_field_bytes: u16, max_jwt_header_b64_bytes: u32): keyless_account::ConfigurationImplementation
public fun new_configuration( override_aud_val: vector<String>, max_signatures_per_txn: u16, max_exp_horizon_secs: u64, training_wheels_pubkey: Option<vector<u8>>, max_commited_epk_bytes: u16, max_iss_val_bytes: u16, max_extra_field_bytes: u16, max_jwt_header_b64_bytes: u32): Configuration { Configuration { override_aud_vals: override_aud_val, max_signatures_per_txn, max_exp_horizon_secs, training_wheels_pubkey, max_commited_epk_bytes, max_iss_val_bytes, max_extra_field_bytes, max_jwt_header_b64_bytes, }}validate_groth16_vk
Pre-validate the VK to actively-prevent incorrect VKs from being set on-chain.
fun validate_groth16_vk(vk: &keyless_account::Groth16VerificationKey)Implementation
fun validate_groth16_vk(vk: &Groth16VerificationKey) { // Could be leveraged to speed up the VM deserialization of the VK by 2x, since it can assume the points are valid. assert!(option::is_some(&crypto_algebra::deserialize<bn254_algebra::G1, bn254_algebra::FormatG1Compr>(&vk.alpha_g1)), E_INVALID_BN254_G1_SERIALIZATION); assert!(option::is_some(&crypto_algebra::deserialize<bn254_algebra::G2, bn254_algebra::FormatG2Compr>(&vk.beta_g2)), E_INVALID_BN254_G2_SERIALIZATION); assert!(option::is_some(&crypto_algebra::deserialize<bn254_algebra::G2, bn254_algebra::FormatG2Compr>(&vk.gamma_g2)), E_INVALID_BN254_G2_SERIALIZATION); assert!(option::is_some(&crypto_algebra::deserialize<bn254_algebra::G2, bn254_algebra::FormatG2Compr>(&vk.delta_g2)), E_INVALID_BN254_G2_SERIALIZATION); for (i in 0..vector::length(&vk.gamma_abc_g1)) { assert!(option::is_some(&crypto_algebra::deserialize<bn254_algebra::G1, bn254_algebra::FormatG1Compr>(vector::borrow(&vk.gamma_abc_g1, i))), E_INVALID_BN254_G1_SERIALIZATION); };}update_groth16_verification_key
Sets the Groth16 verification key, only callable during genesis. To call during governance proposals, use
set_groth16_verification_key_for_next_epoch.
WARNING: See set_groth16_verification_key_for_next_epoch for caveats.
public fun update_groth16_verification_key(fx: &signer, vk: keyless_account::Groth16VerificationKey)Implementation
public fun update_groth16_verification_key(fx: &signer, vk: Groth16VerificationKey) { system_addresses::assert_aptos_framework(fx); chain_status::assert_genesis(); // There should not be a previous resource set here. move_to(fx, vk);}update_configuration
Sets the keyless configuration, only callable during genesis. To call during governance proposals, use
set_configuration_for_next_epoch.
WARNING: See set_configuration_for_next_epoch for caveats.
public fun update_configuration(fx: &signer, config: keyless_account::Configuration)Implementation
public fun update_configuration(fx: &signer, config: Configuration) { system_addresses::assert_aptos_framework(fx); chain_status::assert_genesis(); // There should not be a previous resource set here. move_to(fx, config);}update_training_wheels
#[deprecated]public fun update_training_wheels(fx: &signer, pk: option::Option<vector<u8>>)Implementation
public fun update_training_wheels(fx: &signer, pk: Option<vector<u8>>) acquires Configuration { system_addresses::assert_aptos_framework(fx); chain_status::assert_genesis();
if (option::is_some(&pk)) { assert!(vector::length(option::borrow(&pk)) == 32, E_TRAINING_WHEELS_PK_WRONG_SIZE) };
let config = borrow_global_mut<Configuration>(signer::address_of(fx)); config.training_wheels_pubkey = pk;}update_max_exp_horizon
#[deprecated]public fun update_max_exp_horizon(fx: &signer, max_exp_horizon_secs: u64)Implementation
public fun update_max_exp_horizon(fx: &signer, max_exp_horizon_secs: u64) acquires Configuration { system_addresses::assert_aptos_framework(fx); chain_status::assert_genesis();
let config = borrow_global_mut<Configuration>(signer::address_of(fx)); config.max_exp_horizon_secs = max_exp_horizon_secs;}remove_all_override_auds
#[deprecated]public fun remove_all_override_auds(fx: &signer)Implementation
public fun remove_all_override_auds(fx: &signer) acquires Configuration { system_addresses::assert_aptos_framework(fx); chain_status::assert_genesis();
let config = borrow_global_mut<Configuration>(signer::address_of(fx)); config.override_aud_vals = vector[];}add_override_aud
#[deprecated]public fun add_override_aud(fx: &signer, aud: string::String)Implementation
public fun add_override_aud(fx: &signer, aud: String) acquires Configuration { system_addresses::assert_aptos_framework(fx); chain_status::assert_genesis();
let config = borrow_global_mut<Configuration>(signer::address_of(fx)); vector::push_back(&mut config.override_aud_vals, aud);}set_groth16_verification_key_for_next_epoch
Queues up a change to the Groth16 verification key. The change will only be effective after reconfiguration. Only callable via governance proposal.
WARNING: To mitigate against DoS attacks, a VK change should be done together with a training wheels PK change, so that old ZKPs for the old VK cannot be replayed as potentially-valid ZKPs.
WARNING: If a malicious key is set, this would lead to stolen funds.
public fun set_groth16_verification_key_for_next_epoch(fx: &signer, vk: keyless_account::Groth16VerificationKey)Implementation
public fun set_groth16_verification_key_for_next_epoch(fx: &signer, vk: Groth16VerificationKey) { system_addresses::assert_aptos_framework(fx); config_buffer::upsert<Groth16VerificationKey>(vk);}set_configuration_for_next_epoch
Queues up a change to the keyless configuration. The change will only be effective after reconfiguration. Only callable via governance proposal.
WARNING: A malicious Configuration could lead to DoS attacks, create liveness issues, or enable a malicious
recovery service provider to phish users’ accounts.
public fun set_configuration_for_next_epoch(fx: &signer, config: keyless_account::Configuration)Implementation
public fun set_configuration_for_next_epoch(fx: &signer, config: Configuration) { system_addresses::assert_aptos_framework(fx); config_buffer::upsert<Configuration>(config);}update_training_wheels_for_next_epoch
Convenience method to queue up a change to the training wheels PK. The change will only be effective after reconfiguration. Only callable via governance proposal.
WARNING: If a malicious key is set, this could lead to stolen funds.
public fun update_training_wheels_for_next_epoch(fx: &signer, pk: option::Option<vector<u8>>)Implementation
public fun update_training_wheels_for_next_epoch(fx: &signer, pk: Option<vector<u8>>) acquires Configuration { system_addresses::assert_aptos_framework(fx);
// If a PK is being set, validate it first. if (option::is_some(&pk)) { let bytes = *option::borrow(&pk); let vpk = ed25519::new_validated_public_key_from_bytes(bytes); assert!(option::is_some(&vpk), E_TRAINING_WHEELS_PK_WRONG_SIZE) };
let config = if (config_buffer::does_exist<Configuration>()) { config_buffer::extract_v2<Configuration>() } else { *borrow_global<Configuration>(signer::address_of(fx)) };
config.training_wheels_pubkey = pk;
set_configuration_for_next_epoch(fx, config);}update_max_exp_horizon_for_next_epoch
Convenience method to queues up a change to the max expiration horizon. The change will only be effective after reconfiguration. Only callable via governance proposal.
public fun update_max_exp_horizon_for_next_epoch(fx: &signer, max_exp_horizon_secs: u64)Implementation
public fun update_max_exp_horizon_for_next_epoch(fx: &signer, max_exp_horizon_secs: u64) acquires Configuration { system_addresses::assert_aptos_framework(fx);
let config = if (config_buffer::does_exist<Configuration>()) { config_buffer::extract_v2<Configuration>() } else { *borrow_global<Configuration>(signer::address_of(fx)) };
config.max_exp_horizon_secs = max_exp_horizon_secs;
set_configuration_for_next_epoch(fx, config);}remove_all_override_auds_for_next_epoch
Convenience method to queue up clearing the set of override aud’s. The change will only be effective after
reconfiguration. Only callable via governance proposal.
WARNING: When no override aud is set, recovery of keyless accounts associated with applications that disappeared
is no longer possible.
public fun remove_all_override_auds_for_next_epoch(fx: &signer)Implementation
public fun remove_all_override_auds_for_next_epoch(fx: &signer) acquires Configuration { system_addresses::assert_aptos_framework(fx);
let config = if (config_buffer::does_exist<Configuration>()) { config_buffer::extract_v2<Configuration>() } else { *borrow_global<Configuration>(signer::address_of(fx)) };
config.override_aud_vals = vector[];
set_configuration_for_next_epoch(fx, config);}add_override_aud_for_next_epoch
Convenience method to queue up an append to the set of override aud’s. The change will only be effective
after reconfiguration. Only callable via governance proposal.
WARNING: If a malicious override aud is set, this could lead to stolen funds.
public fun add_override_aud_for_next_epoch(fx: &signer, aud: string::String)Implementation
public fun add_override_aud_for_next_epoch(fx: &signer, aud: String) acquires Configuration { system_addresses::assert_aptos_framework(fx);
let config = if (config_buffer::does_exist<Configuration>()) { config_buffer::extract_v2<Configuration>() } else { *borrow_global<Configuration>(signer::address_of(fx)) };
vector::push_back(&mut config.override_aud_vals, aud);
set_configuration_for_next_epoch(fx, config);}on_new_epoch
Only used in reconfigurations to apply the queued up configuration changes, if there are any.
public(friend) fun on_new_epoch(fx: &signer)Implementation
public(friend) fun on_new_epoch(fx: &signer) acquires Groth16VerificationKey, Configuration { system_addresses::assert_aptos_framework(fx);
if (config_buffer::does_exist<Groth16VerificationKey>()) { let vk = config_buffer::extract_v2(); if (exists<Groth16VerificationKey>(@aptos_framework)) { *borrow_global_mut<Groth16VerificationKey>(@aptos_framework) = vk; } else { move_to(fx, vk); } };
if (config_buffer::does_exist<Configuration>()) { let config = config_buffer::extract_v2(); if (exists<Configuration>(@aptos_framework)) { *borrow_global_mut<Configuration>(@aptos_framework) = config; } else { move_to(fx, config); } };}Specification
pragma verify=false;