Module 0x1::jwks
JWK functions and structs.
Note: An important design constraint for this module is that the JWK consensus Rust code is unable to spawn a VM and make a Move function call. Instead, the JWK consensus Rust code will have to directly write some of the resources in this file. As a result, the structs in this file are declared so as to have a simple layout which is easily accessible in Rust.
- Struct
OIDCProvider - Resource
SupportedOIDCProviders - Struct
UnsupportedJWK - Struct
RSA_JWK - Struct
JWK - Struct
ProviderJWKs - Struct
AllProvidersJWKs - Resource
ObservedJWKs - Struct
ObservedJWKsUpdated - Struct
Patch - Struct
PatchRemoveAll - Struct
PatchRemoveIssuer - Struct
PatchRemoveJWK - Struct
PatchUpsertJWK - Resource
Patches - Resource
PatchedJWKs - Resource
FederatedJWKs - Constants
- Function
patch_federated_jwks - Function
update_federated_jwk_set - Function
get_patched_jwk - Function
try_get_patched_jwk - Function
upsert_oidc_provider - Function
upsert_oidc_provider_for_next_epoch - Function
remove_oidc_provider - Function
remove_oidc_provider_for_next_epoch - Function
on_new_epoch - Function
set_patches - Function
new_patch_remove_all - Function
new_patch_remove_issuer - Function
new_patch_remove_jwk - Function
new_patch_upsert_jwk - Function
new_rsa_jwk - Function
new_unsupported_jwk - Function
initialize - Function
initialize_with_defaults - Function
remove_oidc_provider_internal - Function
upsert_into_observed_jwks - Function
remove_issuer_from_observed_jwks - Function
regenerate_patched_jwks - Function
try_get_jwk_by_issuer - Function
try_get_jwk_by_id - Function
get_jwk_id - Function
upsert_provider_jwks - Function
remove_issuer - Function
upsert_jwk - Function
remove_jwk - Function
apply_patch - Specification
- Function
patch_federated_jwks - Function
update_federated_jwk_set - Function
get_patched_jwk - Function
try_get_patched_jwk - Function
upsert_oidc_provider_for_next_epoch - Function
remove_oidc_provider_for_next_epoch - Function
on_new_epoch - Function
set_patches - Function
initialize_with_defaults - Function
upsert_into_observed_jwks - Function
remove_issuer_from_observed_jwks - Function
regenerate_patched_jwks - Function
try_get_jwk_by_issuer - Function
try_get_jwk_by_id - Function
remove_issuer - Function
remove_jwk - Function
apply_patch
- Function
use 0x1::bcs;
use 0x1::chain_status;
use 0x1::comparator;
use 0x1::config_buffer;
use 0x1::copyable_any;
use 0x1::error;
use 0x1::event;
use 0x1::features;
use 0x1::option;
use 0x1::reconfiguration;
use 0x1::signer;
use 0x1::string;
use 0x1::system_addresses;
use 0x1::vector;
Struct OIDCProvider
An OIDC provider.
struct OIDCProvider has copy, drop, store
Fields
Resource SupportedOIDCProviders
A list of OIDC providers whose JWKs should be watched by validators. Maintained by governance proposals.
struct SupportedOIDCProviders has copy, drop, store, key
Fields
-
providers: vector<jwks::OIDCProvider>
Struct UnsupportedJWK
An JWK variant that represents the JWKs which were observed but not yet supported by Aptos.
Observing UnsupportedJWKs means the providers adopted a new key type/format, and the system should be updated.
struct UnsupportedJWK has copy, drop, store
Struct RSA_JWK
A JWK variant where kty is RSA.
struct RSA_JWK has copy, drop, store
Fields
-
kid: string::String -
kty: string::String -
alg: string::String -
e: string::String -
n: string::String
Struct JWK
A JSON web key.
struct JWK has copy, drop, store
Fields
-
variant: copyable_any::Any -
A
JWKvariant packed as anAny. Currently the variant type is one of the following. -RSA_JWK-UnsupportedJWK
Struct ProviderJWKs
A provider and its JWKs.
struct ProviderJWKs has copy, drop, store
Fields
-
issuer: vector<u8> - The utf-8 encoding of the issuer string (e.g., "https://www.facebook.com").
-
version: u64 - A version number is needed by JWK consensus to dedup the updates. e.g, when on chain version = 5, multiple nodes can propose an update with version = 6. Bumped every time the JWKs for the current issuer is updated. The Rust authenticator only uses the latest version.
-
jwks: vector<jwks::JWK> -
Vector of
JWK's sorted by their unique ID (fromget_jwk_id) in dictionary order.
Struct AllProvidersJWKs
Multiple ProviderJWKs objects, indexed by issuer and key ID.
struct AllProvidersJWKs has copy, drop, store
Fields
-
entries: vector<jwks::ProviderJWKs> -
Vector of
ProviderJWKssorted byProviderJWKs::issuerin dictionary order.
Resource ObservedJWKs
The AllProvidersJWKs that validators observed and agreed on.
struct ObservedJWKs has copy, drop, store, key
Fields
Struct ObservedJWKsUpdated
When ObservedJWKs is updated, this event is sent to resync the JWK consensus state in all validators.
#[event]
struct ObservedJWKsUpdated has drop, store
Fields
-
epoch: u64 -
jwks: jwks::AllProvidersJWKs
Struct Patch
A small edit or patch that is applied to a AllProvidersJWKs to obtain PatchedJWKs.
struct Patch has copy, drop, store
Fields
-
variant: copyable_any::Any -
A
Patchvariant packed as anAny. Currently the variant type is one of the following. -PatchRemoveAll-PatchRemoveIssuer-PatchRemoveJWK-PatchUpsertJWK
Struct PatchRemoveAll
A Patch variant to remove all JWKs.
struct PatchRemoveAll has copy, drop, store
Fields
-
dummy_field: bool
Struct PatchRemoveIssuer
A Patch variant to remove an issuer and all its JWKs.
struct PatchRemoveIssuer has copy, drop, store
Fields
-
issuer: vector<u8>
Struct PatchRemoveJWK
A Patch variant to remove a specific JWK of an issuer.
struct PatchRemoveJWK has copy, drop, store
Struct PatchUpsertJWK
A Patch variant to upsert a JWK for an issuer.
struct PatchUpsertJWK has copy, drop, store
Resource Patches
A sequence of Patch objects that are applied one by one to the ObservedJWKs.
Maintained by governance proposals.
struct Patches has key
Fields
-
patches: vector<jwks::Patch>
Resource PatchedJWKs
The result of applying the Patches to the ObservedJWKs.
This is what applications should consume.
struct PatchedJWKs has drop, key
Fields
Resource FederatedJWKs
JWKs for federated keyless accounts are stored in this resource.
struct FederatedJWKs has drop, key
Fields
Constants
const DELETE_COMMAND_INDICATOR: vector<u8> = [84, 72, 73, 83, 95, 73, 83, 95, 65, 95, 68, 69, 76, 69, 84, 69, 95, 67, 79, 77, 77, 65, 78, 68];
const EFEDERATED_JWKS_TOO_LARGE: u64 = 8;
const EINSTALL_FEDERATED_JWKS_AT_APTOS_FRAMEWORK: u64 = 7;
const EINVALID_FEDERATED_JWK_SET: u64 = 9;
const EISSUER_NOT_FOUND: u64 = 5;
const EJWK_ID_NOT_FOUND: u64 = 6;
const ENATIVE_INCORRECT_VERSION: u64 = 259;
const ENATIVE_MISSING_RESOURCE_OBSERVED_JWKS: u64 = 258;
const ENATIVE_MISSING_RESOURCE_VALIDATOR_SET: u64 = 257;
const ENATIVE_MULTISIG_VERIFICATION_FAILED: u64 = 260;
const ENATIVE_NOT_ENOUGH_VOTING_POWER: u64 = 261;
const EUNEXPECTED_EPOCH: u64 = 1;
const EUNEXPECTED_VERSION: u64 = 2;
const EUNKNOWN_JWK_VARIANT: u64 = 4;
const EUNKNOWN_PATCH_VARIANT: u64 = 3;
We limit the size of a PatchedJWKs resource installed by a dapp owner for federated keyless accounts.
Note: If too large, validators waste work reading it for invalid TXN signatures.
const MAX_FEDERATED_JWKS_SIZE_BYTES: u64 = 2048;
Function patch_federated_jwks
Called by a federated keyless dapp owner to install the JWKs for the federated OIDC provider (e.g., Auth0, AWS
Cognito, etc). For type-safety, we explicitly use a struct FederatedJWKs { jwks: AllProviderJWKs } instead of
reusing PatchedJWKs { jwks: AllProviderJWKs }, which is a JWK-consensus-specific struct.
public fun patch_federated_jwks(jwk_owner: &signer, patches: vector<jwks::Patch>)
Implementation
public fun patch_federated_jwks(jwk_owner: &signer, patches: vector<Patch>) acquires FederatedJWKs {
// Prevents accidental calls in 0x1::jwks that install federated JWKs at the Aptos framework address.
assert!(!system_addresses::is_aptos_framework_address(signer::address_of(jwk_owner)),
error::invalid_argument(EINSTALL_FEDERATED_JWKS_AT_APTOS_FRAMEWORK)
);
let jwk_addr = signer::address_of(jwk_owner);
if (!exists<FederatedJWKs>(jwk_addr)) {
move_to(jwk_owner, FederatedJWKs { jwks: AllProvidersJWKs { entries: vector[] } });
};
let fed_jwks = borrow_global_mut<FederatedJWKs>(jwk_addr);
patches.for_each_ref(|obj|{
let patch: &Patch = obj;
apply_patch(&mut fed_jwks.jwks, *patch);
});
// TODO: Can we check the size more efficiently instead of serializing it via BCS?
let num_bytes = bcs::to_bytes(fed_jwks).length();
assert!(num_bytes < MAX_FEDERATED_JWKS_SIZE_BYTES, error::invalid_argument(EFEDERATED_JWKS_TOO_LARGE));
}
Function update_federated_jwk_set
This can be called to install or update a set of JWKs for a federated OIDC provider. This function should be invoked to intially install a set of JWKs or to update a set of JWKs when a keypair is rotated.
The iss parameter is the value of the iss claim on the JWTs that are to be verified by the JWK set.
kid_vec, alg_vec, e_vec, n_vec are String vectors of the JWK attributes kid, alg, e and n respectively.
See https://datatracker.ietf.org/doc/html/rfc7517#section-4 for more details about the JWK attributes aforementioned.
For the example JWK set snapshot below containing 2 keys for Google found at https://www.googleapis.com/oauth2/v3/certs -
{
"keys": [
{
"alg": "RS256",
"use": "sig",
"kty": "RSA",
"n": "wNHgGSG5B5xOEQNFPW2p_6ZxZbfPoAU5VceBUuNwQWLop0ohW0vpoZLU1tAsq_S9s5iwy27rJw4EZAOGBR9oTRq1Y6Li5pDVJfmzyRNtmWCWndR-bPqhs_dkJU7MbGwcvfLsN9FSHESFrS9sfGtUX-lZfLoGux23TKdYV9EE-H-NDASxrVFUk2GWc3rL6UEMWrMnOqV9-tghybDU3fcRdNTDuXUr9qDYmhmNegYjYu4REGjqeSyIG1tuQxYpOBH-tohtcfGY-oRTS09kgsSS9Q5BRM4qqCkGP28WhlSf4ui0-norS0gKMMI1P_ZAGEsLn9p2TlYMpewvIuhjJs1thw",
"kid": "d7b939771a7800c413f90051012d975981916d71",
"e": "AQAB"
},
{
"kty": "RSA",
"kid": "b2620d5e7f132b52afe8875cdf3776c064249d04",
"alg": "RS256",
"n": "pi22xDdK2fz5gclIbDIGghLDYiRO56eW2GUcboeVlhbAuhuT5mlEYIevkxdPOg5n6qICePZiQSxkwcYMIZyLkZhSJ2d2M6Szx2gDtnAmee6o_tWdroKu0DjqwG8pZU693oLaIjLku3IK20lTs6-2TeH-pUYMjEqiFMhn-hb7wnvH_FuPTjgz9i0rEdw_Hf3Wk6CMypaUHi31y6twrMWq1jEbdQNl50EwH-RQmQ9bs3Wm9V9t-2-_Jzg3AT0Ny4zEDU7WXgN2DevM8_FVje4IgztNy29XUkeUctHsr-431_Iu23JIy6U4Kxn36X3RlVUKEkOMpkDD3kd81JPW4Ger_w",
"e": "AQAB",
"use": "sig"
}
]
}
We can call update_federated_jwk_set for Google’s iss - “https://accounts.google.com” and for each vector
argument kid_vec, alg_vec, e_vec, n_vec, we set in index 0 the corresponding attribute in the first JWK and we set in index 1
the corresponding attribute in the second JWK as shown below.
use std::string::utf8;
aptos_framework::jwks::update_federated_jwk_set(
jwk_owner,
b"https://accounts.google.com",
vector[utf8(b"d7b939771a7800c413f90051012d975981916d71"), utf8(b"b2620d5e7f132b52afe8875cdf3776c064249d04")],
vector[utf8(b"RS256"), utf8(b"RS256")],
vector[utf8(b"AQAB"), utf8(b"AQAB")],
vector[
utf8(b"wNHgGSG5B5xOEQNFPW2p_6ZxZbfPoAU5VceBUuNwQWLop0ohW0vpoZLU1tAsq_S9s5iwy27rJw4EZAOGBR9oTRq1Y6Li5pDVJfmzyRNtmWCWndR-bPqhs_dkJU7MbGwcvfLsN9FSHESFrS9sfGtUX-lZfLoGux23TKdYV9EE-H-NDASxrVFUk2GWc3rL6UEMWrMnOqV9-tghybDU3fcRdNTDuXUr9qDYmhmNegYjYu4REGjqeSyIG1tuQxYpOBH-tohtcfGY-oRTS09kgsSS9Q5BRM4qqCkGP28WhlSf4ui0-norS0gKMMI1P_ZAGEsLn9p2TlYMpewvIuhjJs1thw"),
utf8(b"pi22xDdK2fz5gclIbDIGghLDYiRO56eW2GUcboeVlhbAuhuT5mlEYIevkxdPOg5n6qICePZiQSxkwcYMIZyLkZhSJ2d2M6Szx2gDtnAmee6o_tWdroKu0DjqwG8pZU693oLaIjLku3IK20lTs6-2TeH-pUYMjEqiFMhn-hb7wnvH_FuPTjgz9i0rEdw_Hf3Wk6CMypaUHi31y6twrMWq1jEbdQNl50EwH-RQmQ9bs3Wm9V9t-2-_Jzg3AT0Ny4zEDU7WXgN2DevM8_FVje4IgztNy29XUkeUctHsr-431_Iu23JIy6U4Kxn36X3RlVUKEkOMpkDD3kd81JPW4Ger_w")
]
)
See AIP-96 for more details about federated keyless - https://github.com/aptos-foundation/AIPs/blob/main/aips/aip-96.md
NOTE: Currently only RSA keys are supported.
public entry fun update_federated_jwk_set(jwk_owner: &signer, iss: vector<u8>, kid_vec: vector<string::String>, alg_vec: vector<string::String>, e_vec: vector<string::String>, n_vec: vector<string::String>)
Implementation
public entry fun update_federated_jwk_set(jwk_owner: &signer, iss: vector<u8>, kid_vec: vector<String>, alg_vec: vector<String>, e_vec: vector<String>, n_vec: vector<String>) acquires FederatedJWKs {
assert!(!kid_vec.is_empty(), error::invalid_argument(EINVALID_FEDERATED_JWK_SET));
let num_jwk = kid_vec.length<String>();
assert!(alg_vec.length() == num_jwk , error::invalid_argument(EINVALID_FEDERATED_JWK_SET));
assert!(e_vec.length() == num_jwk, error::invalid_argument(EINVALID_FEDERATED_JWK_SET));
assert!(n_vec.length() == num_jwk, error::invalid_argument(EINVALID_FEDERATED_JWK_SET));
let remove_all_patch = new_patch_remove_all();
let patches = vector[remove_all_patch];
while (!kid_vec.is_empty()) {
let kid = kid_vec.pop_back();
let alg = alg_vec.pop_back();
let e = e_vec.pop_back();
let n = n_vec.pop_back();
let jwk = new_rsa_jwk(kid, alg, e, n);
let patch = new_patch_upsert_jwk(iss, jwk);
patches.push_back(patch)
};
patch_federated_jwks(jwk_owner, patches);
}
Function get_patched_jwk
Get a JWK by issuer and key ID from the PatchedJWKs.
Abort if such a JWK does not exist.
More convenient to call from Rust, since it does not wrap the JWK in an Option.
public fun get_patched_jwk(issuer: vector<u8>, jwk_id: vector<u8>): jwks::JWK
Implementation
public fun get_patched_jwk(issuer: vector<u8>, jwk_id: vector<u8>): JWK acquires PatchedJWKs {
try_get_patched_jwk(issuer, jwk_id).extract()
}
Function try_get_patched_jwk
Get a JWK by issuer and key ID from the PatchedJWKs, if it exists.
More convenient to call from Move, since it does not abort.
public fun try_get_patched_jwk(issuer: vector<u8>, jwk_id: vector<u8>): option::Option<jwks::JWK>
Implementation
public fun try_get_patched_jwk(issuer: vector<u8>, jwk_id: vector<u8>): Option<JWK> acquires PatchedJWKs {
let jwks = &borrow_global<PatchedJWKs>(@aptos_framework).jwks;
try_get_jwk_by_issuer(jwks, issuer, jwk_id)
}
Function upsert_oidc_provider
Deprecated by upsert_oidc_provider_for_next_epoch().
TODO: update all the tests that reference this function, then disable this function.
public fun upsert_oidc_provider(fx: &signer, name: vector<u8>, config_url: vector<u8>): option::Option<vector<u8>>
Implementation
public fun upsert_oidc_provider(fx: &signer, name: vector<u8>, config_url: vector<u8>): Option<vector<u8>> acquires SupportedOIDCProviders {
system_addresses::assert_aptos_framework(fx);
chain_status::assert_genesis();
let provider_set = borrow_global_mut<SupportedOIDCProviders>(@aptos_framework);
let old_config_url= remove_oidc_provider_internal(provider_set, name);
provider_set.providers.push_back(OIDCProvider { name, config_url });
old_config_url
}
Function upsert_oidc_provider_for_next_epoch
Used in on-chain governances to update the supported OIDC providers, effective starting next epoch. Example usage:
aptos_framework::jwks::upsert_oidc_provider_for_next_epoch(
&framework_signer,
b"https://accounts.google.com",
b"https://accounts.google.com/.well-known/openid-configuration"
);
aptos_framework::aptos_governance::reconfigure(&framework_signer);
public fun upsert_oidc_provider_for_next_epoch(fx: &signer, name: vector<u8>, config_url: vector<u8>): option::Option<vector<u8>>
Implementation
public fun upsert_oidc_provider_for_next_epoch(fx: &signer, name: vector<u8>, config_url: vector<u8>): Option<vector<u8>> acquires SupportedOIDCProviders {
system_addresses::assert_aptos_framework(fx);
let provider_set = if (config_buffer::does_exist<SupportedOIDCProviders>()) {
config_buffer::extract_v2<SupportedOIDCProviders>()
} else {
*borrow_global<SupportedOIDCProviders>(@aptos_framework)
};
let old_config_url = remove_oidc_provider_internal(&mut provider_set, name);
provider_set.providers.push_back(OIDCProvider { name, config_url });
config_buffer::upsert(provider_set);
old_config_url
}
Function remove_oidc_provider
Deprecated by remove_oidc_provider_for_next_epoch().
TODO: update all the tests that reference this function, then disable this function.
public fun remove_oidc_provider(fx: &signer, name: vector<u8>): option::Option<vector<u8>>
Implementation
public fun remove_oidc_provider(fx: &signer, name: vector<u8>): Option<vector<u8>> acquires SupportedOIDCProviders {
system_addresses::assert_aptos_framework(fx);
chain_status::assert_genesis();
let provider_set = borrow_global_mut<SupportedOIDCProviders>(@aptos_framework);
remove_oidc_provider_internal(provider_set, name)
}
Function remove_oidc_provider_for_next_epoch
Used in on-chain governances to update the supported OIDC providers, effective starting next epoch. Example usage:
aptos_framework::jwks::remove_oidc_provider_for_next_epoch(
&framework_signer,
b"https://accounts.google.com",
);
aptos_framework::aptos_governance::reconfigure(&framework_signer);
public fun remove_oidc_provider_for_next_epoch(fx: &signer, name: vector<u8>): option::Option<vector<u8>>
Implementation
public fun remove_oidc_provider_for_next_epoch(fx: &signer, name: vector<u8>): Option<vector<u8>> acquires SupportedOIDCProviders {
system_addresses::assert_aptos_framework(fx);
let provider_set = if (config_buffer::does_exist<SupportedOIDCProviders>()) {
config_buffer::extract_v2<SupportedOIDCProviders>()
} else {
*borrow_global<SupportedOIDCProviders>(@aptos_framework)
};
let ret = remove_oidc_provider_internal(&mut provider_set, name);
config_buffer::upsert(provider_set);
ret
}
Function on_new_epoch
Only used in reconfigurations to apply the pending SupportedOIDCProviders, if there is any.
public(friend) fun on_new_epoch(framework: &signer)
Implementation
public(friend) fun on_new_epoch(framework: &signer) acquires SupportedOIDCProviders {
system_addresses::assert_aptos_framework(framework);
if (config_buffer::does_exist<SupportedOIDCProviders>()) {
let new_config = config_buffer::extract_v2<SupportedOIDCProviders>();
if (exists<SupportedOIDCProviders>(@aptos_framework)) {
*borrow_global_mut<SupportedOIDCProviders>(@aptos_framework) = new_config;
} else {
move_to(framework, new_config);
}
}
}
Function set_patches
Set the Patches. Only called in governance proposals.
public fun set_patches(fx: &signer, patches: vector<jwks::Patch>)
Implementation
public fun set_patches(fx: &signer, patches: vector<Patch>) acquires Patches, PatchedJWKs, ObservedJWKs {
system_addresses::assert_aptos_framework(fx);
borrow_global_mut<Patches>(@aptos_framework).patches = patches;
regenerate_patched_jwks();
}
Function new_patch_remove_all
Create a Patch that removes all entries.
public fun new_patch_remove_all(): jwks::Patch
Implementation
public fun new_patch_remove_all(): Patch {
Patch {
variant: copyable_any::pack(PatchRemoveAll {}),
}
}
Function new_patch_remove_issuer
Create a Patch that removes the entry of a given issuer, if exists.
public fun new_patch_remove_issuer(issuer: vector<u8>): jwks::Patch
Implementation
public fun new_patch_remove_issuer(issuer: vector<u8>): Patch {
Patch {
variant: copyable_any::pack(PatchRemoveIssuer { issuer }),
}
}
Function new_patch_remove_jwk
Create a Patch that removes the entry of a given issuer, if exists.
public fun new_patch_remove_jwk(issuer: vector<u8>, jwk_id: vector<u8>): jwks::Patch
Implementation
public fun new_patch_remove_jwk(issuer: vector<u8>, jwk_id: vector<u8>): Patch {
Patch {
variant: copyable_any::pack(PatchRemoveJWK { issuer, jwk_id })
}
}
Function new_patch_upsert_jwk
Create a Patch that upserts a JWK into an issuer’s JWK set.
public fun new_patch_upsert_jwk(issuer: vector<u8>, jwk: jwks::JWK): jwks::Patch
Implementation
public fun new_patch_upsert_jwk(issuer: vector<u8>, jwk: JWK): Patch {
Patch {
variant: copyable_any::pack(PatchUpsertJWK { issuer, jwk })
}
}
Function new_rsa_jwk
Create a JWK of variant RSA_JWK.
public fun new_rsa_jwk(kid: string::String, alg: string::String, e: string::String, n: string::String): jwks::JWK
Implementation
public fun new_rsa_jwk(kid: String, alg: String, e: String, n: String): JWK {
JWK {
variant: copyable_any::pack(RSA_JWK {
kid,
kty: utf8(b"RSA"),
e,
n,
alg,
}),
}
}
Function new_unsupported_jwk
Create a JWK of variant UnsupportedJWK.
public fun new_unsupported_jwk(id: vector<u8>, payload: vector<u8>): jwks::JWK
Implementation
public fun new_unsupported_jwk(id: vector<u8>, payload: vector<u8>): JWK {
JWK {
variant: copyable_any::pack(UnsupportedJWK { id, payload })
}
}
Function initialize
Initialize some JWK resources. Should only be invoked by genesis.
public fun initialize(fx: &signer)
Implementation
public fun initialize(fx: &signer) {
system_addresses::assert_aptos_framework(fx);
move_to(fx, SupportedOIDCProviders { providers: vector[] });
move_to(fx, ObservedJWKs { jwks: AllProvidersJWKs { entries: vector[] } });
move_to(fx, Patches { patches: vector[] });
move_to(fx, PatchedJWKs { jwks: AllProvidersJWKs { entries: vector[] } });
}
Function initialize_with_defaults
Initialize some JWK resources. Should only be invoked by genesis.
public fun initialize_with_defaults(fx: &signer, providers: vector<jwks::OIDCProvider>, patches: vector<jwks::Patch>)
Implementation
public fun initialize_with_defaults(fx: &signer, providers: vector<OIDCProvider>, patches: vector<Patch>) {
system_addresses::assert_aptos_framework(fx);
move_to(fx, SupportedOIDCProviders { providers });
move_to(fx, ObservedJWKs { jwks: AllProvidersJWKs { entries: vector[] } });
move_to(fx, Patches { patches });
move_to(fx, PatchedJWKs { jwks: AllProvidersJWKs { entries: vector[] } });
regenerate_patched_jwks();
}
Function remove_oidc_provider_internal
Helper function that removes an OIDC provider from the SupportedOIDCProviders.
Returns the old config URL of the provider, if any, as an Option.
fun remove_oidc_provider_internal(provider_set: &mut jwks::SupportedOIDCProviders, name: vector<u8>): option::Option<vector<u8>>
Implementation
fun remove_oidc_provider_internal(provider_set: &mut SupportedOIDCProviders, name: vector<u8>): Option<vector<u8>> {
let (name_exists, idx) = provider_set.providers.find(|obj| {
let provider: &OIDCProvider = obj;
provider.name == name
});
if (name_exists) {
let old_provider = provider_set.providers.swap_remove(idx);
option::some(old_provider.config_url)
} else {
option::none()
}
}
Function upsert_into_observed_jwks
Only used by validators to publish their observed JWK update.
NOTE: It is assumed verification has been done to ensure each update is quorum-certified,
and its version equals to the on-chain version + 1.
public fun upsert_into_observed_jwks(fx: &signer, provider_jwks_vec: vector<jwks::ProviderJWKs>)
Implementation
public fun upsert_into_observed_jwks(fx: &signer, provider_jwks_vec: vector<ProviderJWKs>) acquires ObservedJWKs, PatchedJWKs, Patches {
system_addresses::assert_aptos_framework(fx);
let observed_jwks = borrow_global_mut<ObservedJWKs>(@aptos_framework);
if (features::is_jwk_consensus_per_key_mode_enabled()) {
provider_jwks_vec.for_each(|proposed_provider_jwks|{
let maybe_cur_issuer_jwks = remove_issuer(&mut observed_jwks.jwks, proposed_provider_jwks.issuer);
let cur_issuer_jwks = if (option::is_some(&maybe_cur_issuer_jwks)) {
option::extract(&mut maybe_cur_issuer_jwks)
} else {
ProviderJWKs {
issuer: proposed_provider_jwks.issuer,
version: 0,
jwks: vector[],
}
};
assert!(cur_issuer_jwks.version + 1 == proposed_provider_jwks.version, error::invalid_argument(EUNEXPECTED_VERSION));
vector::for_each(proposed_provider_jwks.jwks, |jwk|{
let variant_type_name = *string::bytes(copyable_any::type_name(&jwk.variant));
let is_delete = if (variant_type_name == b"0x1::jwks::UnsupportedJWK") {
let repr = copyable_any::unpack<UnsupportedJWK>(jwk.variant);
&repr.payload == &DELETE_COMMAND_INDICATOR
} else {
false
};
if (is_delete) {
remove_jwk(&mut cur_issuer_jwks, get_jwk_id(&jwk));
} else {
upsert_jwk(&mut cur_issuer_jwks, jwk);
}
});
cur_issuer_jwks.version += 1;
upsert_provider_jwks(&mut observed_jwks.jwks, cur_issuer_jwks);
});
} else {
provider_jwks_vec.for_each(|provider_jwks| {
upsert_provider_jwks(&mut observed_jwks.jwks, provider_jwks);
});
};
let epoch = reconfiguration::current_epoch();
emit(ObservedJWKsUpdated { epoch, jwks: observed_jwks.jwks });
regenerate_patched_jwks();
}
Function remove_issuer_from_observed_jwks
Only used by governance to delete an issuer from ObservedJWKs, if it exists.
Return the potentially existing ProviderJWKs of the given issuer.
public fun remove_issuer_from_observed_jwks(fx: &signer, issuer: vector<u8>): option::Option<jwks::ProviderJWKs>
Implementation
public fun remove_issuer_from_observed_jwks(fx: &signer, issuer: vector<u8>): Option<ProviderJWKs> acquires ObservedJWKs, PatchedJWKs, Patches {
system_addresses::assert_aptos_framework(fx);
let observed_jwks = borrow_global_mut<ObservedJWKs>(@aptos_framework);
let old_value = remove_issuer(&mut observed_jwks.jwks, issuer);
let epoch = reconfiguration::current_epoch();
emit(ObservedJWKsUpdated { epoch, jwks: observed_jwks.jwks });
regenerate_patched_jwks();
old_value
}
Function regenerate_patched_jwks
Regenerate PatchedJWKs from ObservedJWKs and Patches and save the result.
fun regenerate_patched_jwks()
Implementation
fun regenerate_patched_jwks() acquires PatchedJWKs, Patches, ObservedJWKs {
let jwks = borrow_global<ObservedJWKs>(@aptos_framework).jwks;
let patches = borrow_global<Patches>(@aptos_framework);
patches.patches.for_each_ref(|obj|{
let patch: &Patch = obj;
apply_patch(&mut jwks, *patch);
});
*borrow_global_mut<PatchedJWKs>(@aptos_framework) = PatchedJWKs { jwks };
}
Function try_get_jwk_by_issuer
Get a JWK by issuer and key ID from an AllProvidersJWKs, if it exists.
fun try_get_jwk_by_issuer(jwks: &jwks::AllProvidersJWKs, issuer: vector<u8>, jwk_id: vector<u8>): option::Option<jwks::JWK>
Implementation
fun try_get_jwk_by_issuer(jwks: &AllProvidersJWKs, issuer: vector<u8>, jwk_id: vector<u8>): Option<JWK> {
let (issuer_found, index) = jwks.entries.find(|obj| {
let provider_jwks: &ProviderJWKs = obj;
issuer == provider_jwks.issuer
});
if (issuer_found) {
try_get_jwk_by_id(jwks.entries.borrow(index), jwk_id)
} else {
option::none()
}
}
Function try_get_jwk_by_id
Get a JWK by key ID from a ProviderJWKs, if it exists.
fun try_get_jwk_by_id(provider_jwks: &jwks::ProviderJWKs, jwk_id: vector<u8>): option::Option<jwks::JWK>
Implementation
fun try_get_jwk_by_id(provider_jwks: &ProviderJWKs, jwk_id: vector<u8>): Option<JWK> {
let (jwk_id_found, index) = provider_jwks.jwks.find(|obj|{
let jwk: &JWK = obj;
jwk_id == get_jwk_id(jwk)
});
if (jwk_id_found) {
option::some(provider_jwks.jwks[index])
} else {
option::none()
}
}
Function get_jwk_id
Get the ID of a JWK.
fun get_jwk_id(jwk: &jwks::JWK): vector<u8>
Implementation
fun get_jwk_id(jwk: &JWK): vector<u8> {
let variant_type_name = *jwk.variant.type_name().bytes();
if (variant_type_name == b"0x1::jwks::RSA_JWK") {
let rsa = jwk.variant.unpack<RSA_JWK>();
*rsa.kid.bytes()
} else if (variant_type_name == b"0x1::jwks::UnsupportedJWK") {
let unsupported = jwk.variant.unpack<UnsupportedJWK>();
unsupported.id
} else {
abort(error::invalid_argument(EUNKNOWN_JWK_VARIANT))
}
}
Function upsert_provider_jwks
Upsert a ProviderJWKs into an AllProvidersJWKs. If this upsert replaced an existing entry, return it.
Maintains the sorted-by-issuer invariant in AllProvidersJWKs.
fun upsert_provider_jwks(jwks: &mut jwks::AllProvidersJWKs, provider_jwks: jwks::ProviderJWKs): option::Option<jwks::ProviderJWKs>
Implementation
fun upsert_provider_jwks(jwks: &mut AllProvidersJWKs, provider_jwks: ProviderJWKs): Option<ProviderJWKs> {
// NOTE: Using a linear-time search here because we do not expect too many providers.
let found = false;
let index = 0;
let num_entries = jwks.entries.length();
while (index < num_entries) {
let cur_entry = jwks.entries.borrow(index);
let comparison = compare_u8_vector(provider_jwks.issuer, cur_entry.issuer);
if (comparison.is_greater_than()) {
index += 1;
} else {
found = comparison.is_equal();
break
}
};
// Now if `found == true`, `index` points to the JWK we want to update/remove; otherwise, `index` points to
// where we want to insert.
let ret = if (found) {
let entry = jwks.entries.borrow_mut(index);
let old_entry = option::some(*entry);
*entry = provider_jwks;
old_entry
} else {
jwks.entries.insert(index, provider_jwks);
option::none()
};
ret
}
Function remove_issuer
Remove the entry of an issuer from a AllProvidersJWKs and return the entry, if exists.
Maintains the sorted-by-issuer invariant in AllProvidersJWKs.
fun remove_issuer(jwks: &mut jwks::AllProvidersJWKs, issuer: vector<u8>): option::Option<jwks::ProviderJWKs>
Implementation
fun remove_issuer(jwks: &mut AllProvidersJWKs, issuer: vector<u8>): Option<ProviderJWKs> {
let (found, index) = jwks.entries.find(|obj| {
let provider_jwk_set: &ProviderJWKs = obj;
provider_jwk_set.issuer == issuer
});
let ret = if (found) {
option::some(jwks.entries.remove(index))
} else {
option::none()
};
ret
}
Function upsert_jwk
Upsert a JWK into a ProviderJWKs. If this upsert replaced an existing entry, return it.
fun upsert_jwk(set: &mut jwks::ProviderJWKs, jwk: jwks::JWK): option::Option<jwks::JWK>
Implementation
fun upsert_jwk(set: &mut ProviderJWKs, jwk: JWK): Option<JWK> {
let found = false;
let index = 0;
let num_entries = set.jwks.length();
while (index < num_entries) {
let cur_entry = set.jwks.borrow(index);
let comparison = compare_u8_vector(get_jwk_id(&jwk), get_jwk_id(cur_entry));
if (comparison.is_greater_than()) {
index += 1;
} else {
found = comparison.is_equal();
break
}
};
// Now if `found == true`, `index` points to the JWK we want to update/remove; otherwise, `index` points to
// where we want to insert.
let ret = if (found) {
let entry = set.jwks.borrow_mut(index);
let old_entry = option::some(*entry);
*entry = jwk;
old_entry
} else {
set.jwks.insert(index, jwk);
option::none()
};
ret
}
Function remove_jwk
Remove the entry of a key ID from a ProviderJWKs and return the entry, if exists.
fun remove_jwk(jwks: &mut jwks::ProviderJWKs, jwk_id: vector<u8>): option::Option<jwks::JWK>
Implementation
fun remove_jwk(jwks: &mut ProviderJWKs, jwk_id: vector<u8>): Option<JWK> {
let (found, index) = jwks.jwks.find(|obj| {
let jwk: &JWK = obj;
jwk_id == get_jwk_id(jwk)
});
let ret = if (found) {
option::some(jwks.jwks.remove(index))
} else {
option::none()
};
ret
}
Function apply_patch
Modify an AllProvidersJWKs object with a Patch.
Maintains the sorted-by-issuer invariant in AllProvidersJWKs.
fun apply_patch(jwks: &mut jwks::AllProvidersJWKs, patch: jwks::Patch)
Implementation
fun apply_patch(jwks: &mut AllProvidersJWKs, patch: Patch) {
let variant_type_name = *patch.variant.type_name().bytes();
if (variant_type_name == b"0x1::jwks::PatchRemoveAll") {
jwks.entries = vector[];
} else if (variant_type_name == b"0x1::jwks::PatchRemoveIssuer") {
let cmd = patch.variant.unpack<PatchRemoveIssuer>();
remove_issuer(jwks, cmd.issuer);
} else if (variant_type_name == b"0x1::jwks::PatchRemoveJWK") {
let cmd = patch.variant.unpack<PatchRemoveJWK>();
// TODO: This is inefficient: we remove the issuer, modify its JWKs & and reinsert the updated issuer. Why
// not just update it in place?
let existing_jwk_set = remove_issuer(jwks, cmd.issuer);
if (existing_jwk_set.is_some()) {
let jwk_set = existing_jwk_set.extract();
remove_jwk(&mut jwk_set, cmd.jwk_id);
upsert_provider_jwks(jwks, jwk_set);
};
} else if (variant_type_name == b"0x1::jwks::PatchUpsertJWK") {
let cmd = patch.variant.unpack<PatchUpsertJWK>();
// TODO: This is inefficient: we remove the issuer, modify its JWKs & and reinsert the updated issuer. Why
// not just update it in place?
let existing_jwk_set = remove_issuer(jwks, cmd.issuer);
let jwk_set = if (existing_jwk_set.is_some()) {
existing_jwk_set.extract()
} else {
ProviderJWKs {
version: 0,
issuer: cmd.issuer,
jwks: vector[],
}
};
upsert_jwk(&mut jwk_set, cmd.jwk);
upsert_provider_jwks(jwks, jwk_set);
} else {
abort(std::error::invalid_argument(EUNKNOWN_PATCH_VARIANT))
}
}
Specification
Function patch_federated_jwks
public fun patch_federated_jwks(jwk_owner: &signer, patches: vector<jwks::Patch>)
pragma verify_duration_estimate = 80;
Function update_federated_jwk_set
public entry fun update_federated_jwk_set(jwk_owner: &signer, iss: vector<u8>, kid_vec: vector<string::String>, alg_vec: vector<string::String>, e_vec: vector<string::String>, n_vec: vector<string::String>)
pragma verify_duration_estimate = 80;
Function get_patched_jwk
public fun get_patched_jwk(issuer: vector<u8>, jwk_id: vector<u8>): jwks::JWK
pragma verify_duration_estimate = 80;
Function try_get_patched_jwk
public fun try_get_patched_jwk(issuer: vector<u8>, jwk_id: vector<u8>): option::Option<jwks::JWK>
pragma verify_duration_estimate = 80;
Function upsert_oidc_provider_for_next_epoch
public fun upsert_oidc_provider_for_next_epoch(fx: &signer, name: vector<u8>, config_url: vector<u8>): option::Option<vector<u8>>
pragma verify = false;
Function remove_oidc_provider_for_next_epoch
public fun remove_oidc_provider_for_next_epoch(fx: &signer, name: vector<u8>): option::Option<vector<u8>>
pragma verify = false;
Function on_new_epoch
public(friend) fun on_new_epoch(framework: &signer)
requires @aptos_framework == std::signer::address_of(framework);
include config_buffer::OnNewEpochRequirement<SupportedOIDCProviders>;
aborts_if false;
Function set_patches
public fun set_patches(fx: &signer, patches: vector<jwks::Patch>)
pragma verify_duration_estimate = 80;
Function initialize_with_defaults
public fun initialize_with_defaults(fx: &signer, providers: vector<jwks::OIDCProvider>, patches: vector<jwks::Patch>)
pragma verify = false;
Function upsert_into_observed_jwks
public fun upsert_into_observed_jwks(fx: &signer, provider_jwks_vec: vector<jwks::ProviderJWKs>)
pragma verify_duration_estimate = 80;
Function remove_issuer_from_observed_jwks
public fun remove_issuer_from_observed_jwks(fx: &signer, issuer: vector<u8>): option::Option<jwks::ProviderJWKs>
pragma verify_duration_estimate = 80;
Function regenerate_patched_jwks
fun regenerate_patched_jwks()
pragma verify_duration_estimate = 80;
Function try_get_jwk_by_issuer
fun try_get_jwk_by_issuer(jwks: &jwks::AllProvidersJWKs, issuer: vector<u8>, jwk_id: vector<u8>): option::Option<jwks::JWK>
pragma verify_duration_estimate = 80;
Function try_get_jwk_by_id
fun try_get_jwk_by_id(provider_jwks: &jwks::ProviderJWKs, jwk_id: vector<u8>): option::Option<jwks::JWK>
pragma verify_duration_estimate = 80;
Function remove_issuer
fun remove_issuer(jwks: &mut jwks::AllProvidersJWKs, issuer: vector<u8>): option::Option<jwks::ProviderJWKs>
pragma opaque;
ensures option::is_none(result) <==> (forall jwk: ProviderJWKs where vector::spec_contains(old(jwks).entries, jwk): jwk.issuer != issuer);
ensures option::is_none(result) ==> old(jwks) == jwks;
ensures option::is_some(result) ==> vector::spec_contains(old(jwks).entries, option::borrow(result));
Function remove_jwk
fun remove_jwk(jwks: &mut jwks::ProviderJWKs, jwk_id: vector<u8>): option::Option<jwks::JWK>
pragma verify_duration_estimate = 80;
Function apply_patch
fun apply_patch(jwks: &mut jwks::AllProvidersJWKs, patch: jwks::Patch)
pragma verify_duration_estimate = 80;