account - [mainnet]
use 0x1::bcs;use 0x1::chain_id;use 0x1::create_signer;use 0x1::ed25519;use 0x1::error;use 0x1::event;use 0x1::features;use 0x1::from_bcs;use 0x1::guid;use 0x1::hash;use 0x1::multi_ed25519;use 0x1::multi_key;use 0x1::option;use 0x1::permissioned_signer;use 0x1::signer;use 0x1::single_key;use 0x1::system_addresses;use 0x1::table;use 0x1::type_info;use 0x1::vector;
Constants
const MAX_U64: u128 = 18446744073709551615;
Scheme identifier used when hashing an account’s address together with a seed to derive the address (not the authentication key) of a resource account. This is an abuse of the notion of a scheme identifier which, for now, serves to domain separate hashes used to derive resource account addresses from hashes used to derive authentication keys. Without such separation, an adversary could create (and get a signer for) a resource account whose address matches an existing address of a MultiEd25519 wallet.
const DERIVE_RESOURCE_ACCOUNT_SCHEME: u8 = 255;
Account already exists
const EACCOUNT_ALREADY_EXISTS: u64 = 1;
An attempt to create a resource account on an account that has a committed transaction
const EACCOUNT_ALREADY_USED: u64 = 16;
Account does not exist
const EACCOUNT_DOES_NOT_EXIST: u64 = 2;
Cannot create account because address is reserved
const ECANNOT_RESERVED_ADDRESS: u64 = 5;
Scheme identifier for Ed25519 signatures used to derive authentication keys for Ed25519 public keys.
const ED25519_SCHEME: u8 = 0;
const EEXCEEDED_MAX_GUID_CREATION_NUM: u64 = 20;
The caller does not have a valid rotation capability offer from the other account
const EINVALID_ACCEPT_ROTATION_CAPABILITY: u64 = 10;
Abort the transaction if the expected originating address is different from the originating address on-chain
const EINVALID_ORIGINATING_ADDRESS: u64 = 13;
Specified proof of knowledge required to prove ownership of a public key is invalid
const EINVALID_PROOF_OF_KNOWLEDGE: u64 = 8;
Specified scheme required to proceed with the smart contract operation - can only be ED25519_SCHEME(0) OR MULTI_ED25519_SCHEME(1)
const EINVALID_SCHEME: u64 = 12;
The provided authentication key has an invalid length
const EMALFORMED_AUTHENTICATION_KEY: u64 = 4;
The new authentication key already has an entry in the OriginatingAddress
table
const ENEW_AUTH_KEY_ALREADY_MAPPED: u64 = 21;
The current authentication key and the new authentication key are the same
const ENEW_AUTH_KEY_SAME_AS_CURRENT: u64 = 22;
The provided public key is not a single Keyless public key
const ENOT_A_KEYLESS_PUBLIC_KEY: u64 = 25;
The provided public key is not the original public key for the account
const ENOT_THE_ORIGINAL_PUBLIC_KEY: u64 = 26;
Current permissioned signer cannot perform the privilaged operations.
const ENO_ACCOUNT_PERMISSION: u64 = 23;
The caller does not have a digital-signature-based capability to call this function
const ENO_CAPABILITY: u64 = 9;
const ENO_SIGNER_CAPABILITY_OFFERED: u64 = 19;
The specified rotation capability offer does not exist at the specified offerer address
const ENO_SUCH_ROTATION_CAPABILITY_OFFER: u64 = 18;
The signer capability offer doesn’t exist at the given address
const ENO_SUCH_SIGNER_CAPABILITY: u64 = 14;
Address to create is not a valid reserved address for Aptos framework
const ENO_VALID_FRAMEWORK_RESERVED_ADDRESS: u64 = 11;
Offerer address doesn’t exist
const EOFFERER_ADDRESS_DOES_NOT_EXIST: u64 = 17;
Transaction exceeded its allocated max gas
const EOUT_OF_GAS: u64 = 6;
An attempt to create a resource account on a claimed account
const ERESOURCE_ACCCOUNT_EXISTS: u64 = 15;
Sequence number exceeds the maximum value for a u64
const ESEQUENCE_NUMBER_TOO_BIG: u64 = 3;
The set_originating_address is disabled due to potential poisoning from account abstraction
const ESET_ORIGINATING_ADDRESS_DISABLED: u64 = 27;
Specified scheme is not recognized. Should be ED25519_SCHEME(0), MULTI_ED25519_SCHEME(1), SINGLE_KEY_SCHEME(2), or MULTI_KEY_SCHEME(3).
const EUNRECOGNIZED_SCHEME: u64 = 24;
Specified current public key is not correct
const EWRONG_CURRENT_PUBLIC_KEY: u64 = 7;
Explicitly separate the GUID space between Object and Account to prevent accidental overlap.
const MAX_GUID_CREATION_NUM: u64 = 1125899906842624;
Scheme identifier for MultiEd25519 signatures used to derive authentication keys for MultiEd25519 public keys.
const MULTI_ED25519_SCHEME: u8 = 1;
Scheme identifier for multi key public keys used to derive authentication keys for multi key public keys.
const MULTI_KEY_SCHEME: u8 = 3;
Scheme identifier for single key public keys used to derive authentication keys for single key public keys.
const SINGLE_KEY_SCHEME: u8 = 2;
const ZERO_AUTH_KEY: vector<u8> = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
Structs
KeyRotation
#[event]struct KeyRotation has drop, store
KeyRotationToPublicKey
#[event]struct KeyRotationToPublicKey has drop, store
Fields
KeyRotationEvent
struct KeyRotationEvent has drop, store
CoinRegisterEvent
struct CoinRegisterEvent has drop, store
Fields
CoinRegister
#[event]struct CoinRegister has drop, store
Fields
-
account: address
-
type_info: type_info::TypeInfo
CapabilityOffer
struct CapabilityOffer<T> has store
Fields
-
for: option::Option<address>
RotationCapability
struct RotationCapability has drop, store
Fields
-
account: address
SignerCapability
struct SignerCapability has drop, store
Fields
-
account: address
RotationProofChallenge
This structs stores the challenge message that should be signed during key rotation. First, this struct is signed by the account owner’s current public key, which proves possession of a capability to rotate the key. Second, this struct is signed by the new public key that the account owner wants to rotate to, which proves knowledge of this new public key’s associated secret key. These two signatures cannot be replayed in another context because they include the TXN’s unique sequence number.
struct RotationProofChallenge has copy, drop
Fields
-
sequence_number: u64
-
originator: address
-
current_auth_key: address
-
new_public_key: vector<u8>
RotationCapabilityOfferProofChallenge
Deprecated struct - newest version is RotationCapabilityOfferProofChallengeV2
struct RotationCapabilityOfferProofChallenge has drop
Fields
-
sequence_number: u64
-
recipient_address: address
SignerCapabilityOfferProofChallenge
Deprecated struct - newest version is SignerCapabilityOfferProofChallengeV2
struct SignerCapabilityOfferProofChallenge has drop
Fields
-
sequence_number: u64
-
recipient_address: address
RotationCapabilityOfferProofChallengeV2
This struct stores the challenge message that should be signed by the source account, when the source account
is delegating its rotation capability to the recipient_address
.
This V2 struct adds the chain_id
and source_address
to the challenge message, which prevents replaying the challenge message.
struct RotationCapabilityOfferProofChallengeV2 has drop
Fields
-
chain_id: u8
-
sequence_number: u64
-
source_address: address
-
recipient_address: address
SignerCapabilityOfferProofChallengeV2
struct SignerCapabilityOfferProofChallengeV2 has copy, drop
Fields
-
sequence_number: u64
-
source_address: address
-
recipient_address: address
Resources
Account
Resource representing an account.
struct Account has store, key
Fields
-
authentication_key: vector<u8>
-
sequence_number: u64
-
guid_creation_num: u64
-
coin_register_events: event::EventHandle<account::CoinRegisterEvent>
-
key_rotation_events: event::EventHandle<account::KeyRotationEvent>
-
rotation_capability_offer: account::CapabilityOffer<account::RotationCapability>
-
signer_capability_offer: account::CapabilityOffer<account::SignerCapability>
OriginatingAddress
It is easy to fetch the authentication key of an address by simply reading it from the Account
struct at that address.
The table in this struct makes it possible to do a reverse lookup: it maps an authentication key, to the address of the account which has that authentication key set.
This mapping is needed when recovering wallets for accounts whose authentication key has been rotated.
For example, imagine a freshly-created wallet with address a
and thus also with authentication key a
, derived from a PK pk_a
with corresponding SK sk_a
.
It is easy to recover such a wallet given just the secret key sk_a
, since the PK can be derived from the SK, the authentication key can then be derived from the PK, and the address equals the authentication key (since there was no key rotation).
However, if such a wallet rotates its authentication key to b
derived from a different PK pk_b
with SK sk_b
, how would account recovery work?
The recovered address would no longer be ‘a’; it would be b
, which is incorrect.
This struct solves this problem by mapping the new authentication key b
to the original address a
and thus helping the wallet software during recovery find the correct address.
struct OriginatingAddress has key
Fields
-
address_map: table::Table<address, address>
Functions
check_rotation_permission
Permissions
fun check_rotation_permission(s: &signer)
Implementation
inline fun check_rotation_permission(s: &signer) { assert!( permissioned_signer::check_permission_exists(s, AccountPermission::KeyRotation {}), error::permission_denied(ENO_ACCOUNT_PERMISSION), );}
check_offering_permission
fun check_offering_permission(s: &signer)
Implementation
inline fun check_offering_permission(s: &signer) { assert!( permissioned_signer::check_permission_exists(s, AccountPermission::Offering {}), error::permission_denied(ENO_ACCOUNT_PERMISSION), );}
grant_key_rotation_permission
Grant permission to perform key rotations on behalf of the master signer.
This is extremely dangerous and should be granted only when it’s absolutely needed.
public fun grant_key_rotation_permission(master: &signer, permissioned_signer: &signer)
Implementation
public fun grant_key_rotation_permission(master: &signer, permissioned_signer: &signer) { permissioned_signer::authorize_unlimited(master, permissioned_signer, AccountPermission::KeyRotation {})}
grant_key_offering_permission
Grant permission to use offered address’s signer on behalf of the master signer.
This is extremely dangerous and should be granted only when it’s absolutely needed.
public fun grant_key_offering_permission(master: &signer, permissioned_signer: &signer)
Implementation
public fun grant_key_offering_permission(master: &signer, permissioned_signer: &signer) { permissioned_signer::authorize_unlimited(master, permissioned_signer, AccountPermission::Offering {})}
initialize
Only called during genesis to initialize system resources for this module.
public(friend) fun initialize(aptos_framework: &signer)
Implementation
public(friend) fun initialize(aptos_framework: &signer) { system_addresses::assert_aptos_framework(aptos_framework); move_to(aptos_framework, OriginatingAddress { address_map: table::new(), });}
create_account_if_does_not_exist
public fun create_account_if_does_not_exist(account_address: address)
Implementation
public fun create_account_if_does_not_exist(account_address: address) { if (!resource_exists_at(account_address)) { assert!( account_address != @vm_reserved && account_address != @aptos_framework && account_address != @aptos_token, error::invalid_argument(ECANNOT_RESERVED_ADDRESS) ); create_account_unchecked(account_address); }}
create_account
Publishes a new Account
resource under new_address
. A signer representing new_address
is returned. This way, the caller of this function can publish additional resources under
new_address
.
public(friend) fun create_account(new_address: address): signer
Implementation
public(friend) fun create_account(new_address: address): signer { // there cannot be an Account resource under new_addr already. assert!(!exists<Account>(new_address), error::already_exists(EACCOUNT_ALREADY_EXISTS)); // NOTE: @core_resources gets created via a `create_account` call, so we do not include it below. assert!( new_address != @vm_reserved && new_address != @aptos_framework && new_address != @aptos_token, error::invalid_argument(ECANNOT_RESERVED_ADDRESS) ); if (features::is_default_account_resource_enabled()) { create_signer(new_address) } else { create_account_unchecked(new_address) }}
create_account_unchecked
fun create_account_unchecked(new_address: address): signer
Implementation
fun create_account_unchecked(new_address: address): signer { let new_account = create_signer(new_address); let authentication_key = bcs::to_bytes(&new_address); assert!( authentication_key.length() == 32, error::invalid_argument(EMALFORMED_AUTHENTICATION_KEY) );
let guid_creation_num = 0;
let guid_for_coin = guid::create(new_address, &mut guid_creation_num); let coin_register_events = event::new_event_handle<CoinRegisterEvent>(guid_for_coin);
let guid_for_rotation = guid::create(new_address, &mut guid_creation_num); let key_rotation_events = event::new_event_handle<KeyRotationEvent>(guid_for_rotation);
move_to( &new_account, Account { authentication_key, sequence_number: 0, guid_creation_num, coin_register_events, key_rotation_events, rotation_capability_offer: CapabilityOffer { for: option::none() }, signer_capability_offer: CapabilityOffer { for: option::none() }, } );
new_account}
exists_at
Returns whether an account exists at addr
.
When the default_account_resource
feature flag is enabled:
- Always returns true, indicating that any address can be treated as a valid account
- This reflects a change in the account model where accounts are now considered to exist implicitly
- The sequence number and other account properties will return default values (0) for addresses without an Account resource
When the feature flag is disabled:
- Returns true only if an Account resource exists at
addr
- This is the legacy behavior where accounts must be explicitly created
#[view]public fun exists_at(addr: address): bool
Implementation
public fun exists_at(addr: address): bool { features::is_default_account_resource_enabled() || exists<Account>(addr)}
resource_exists_at
Returns whether an Account resource exists at addr
.
Unlike exists_at
, this function strictly checks for the presence of the Account resource,
regardless of the default_account_resource
feature flag.
This is useful for operations that specifically need to know if the Account resource has been created, rather than just whether the address can be treated as an account.
fun resource_exists_at(addr: address): bool
Implementation
inline fun resource_exists_at(addr: address): bool { exists<Account>(addr)}
get_guid_next_creation_num
Returns the next GUID creation number for addr
.
When the default_account_resource
feature flag is enabled:
- Returns 0 for addresses without an Account resource
- This allows GUID creation for previously non-existent accounts
- The first GUID created will start the sequence from 0
When the feature flag is disabled:
- Aborts if no Account resource exists at
addr
#[view]public fun get_guid_next_creation_num(addr: address): u64
Implementation
public fun get_guid_next_creation_num(addr: address): u64 acquires Account { if (resource_exists_at(addr)) { Account[addr].guid_creation_num } else if (features::is_default_account_resource_enabled()) { 0 } else { abort error::not_found(EACCOUNT_DOES_NOT_EXIST) }}
get_sequence_number
#[view]public fun get_sequence_number(addr: address): u64
Implementation
public fun get_sequence_number(addr: address): u64 acquires Account { if (resource_exists_at(addr)) { Account[addr].sequence_number } else if (features::is_default_account_resource_enabled()) { 0 } else { abort error::not_found(EACCOUNT_DOES_NOT_EXIST) }}
originating_address
#[view]public fun originating_address(auth_key: address): option::Option<address>
Implementation
public fun originating_address(auth_key: address): Option<address> acquires OriginatingAddress { let address_map_ref = &OriginatingAddress[@aptos_framework].address_map; if (address_map_ref.contains(auth_key)) { option::some(*address_map_ref.borrow(auth_key)) } else { option::none() }}
ensure_resource_exists
fun ensure_resource_exists(addr: address)
Implementation
inline fun ensure_resource_exists(addr: address) acquires Account{ if (features::is_default_account_resource_enabled()) { create_account_if_does_not_exist(addr); } else { assert!(exists_at(addr), error::not_found(EACCOUNT_DOES_NOT_EXIST)); }}
increment_sequence_number
public(friend) fun increment_sequence_number(addr: address)
Implementation
public(friend) fun increment_sequence_number(addr: address) acquires Account { ensure_resource_exists(addr); let sequence_number = &mut Account[addr].sequence_number;
assert!( (*sequence_number as u128) < MAX_U64, error::out_of_range(ESEQUENCE_NUMBER_TOO_BIG) );
*sequence_number = *sequence_number + 1;}
get_authentication_key
#[view]public fun get_authentication_key(addr: address): vector<u8>
Implementation
public fun get_authentication_key(addr: address): vector<u8> acquires Account { if (resource_exists_at(addr)) { Account[addr].authentication_key } else if (features::is_default_account_resource_enabled()) { bcs::to_bytes(&addr) } else { abort error::not_found(EACCOUNT_DOES_NOT_EXIST) }}
rotate_authentication_key_internal
This function is used to rotate a resource account’s authentication key to new_auth_key
. This is done in
many contexts:
- During normal key rotation via
rotate_authentication_key
orrotate_authentication_key_call
- During resource account initialization so that no private key can control the resource account
- During multisig_v2 account creation
public(friend) fun rotate_authentication_key_internal(account: &signer, new_auth_key: vector<u8>)
Implementation
public(friend) fun rotate_authentication_key_internal(account: &signer, new_auth_key: vector<u8>) acquires Account { let addr = signer::address_of(account); ensure_resource_exists(addr); assert!( new_auth_key.length() == 32, error::invalid_argument(EMALFORMED_AUTHENTICATION_KEY) ); check_rotation_permission(account); let account_resource = &mut Account[addr]; account_resource.authentication_key = new_auth_key;}
rotate_authentication_key_call
Private entry function for key rotation that allows the signer to update their authentication key.
Note that this does not update the OriginatingAddress
table because the new_auth_key
is not “verified”: it
does not come with a proof-of-knowledge of the underlying SK. Nonetheless, we need this functionality due to
the introduction of non-standard key algorithms, such as passkeys, which cannot produce proofs-of-knowledge in
the format expected in rotate_authentication_key
.
If you’d like to followup with updating the OriginatingAddress
table, you can call
set_originating_address()
.
entry fun rotate_authentication_key_call(account: &signer, new_auth_key: vector<u8>)
Implementation
entry fun rotate_authentication_key_call(account: &signer, new_auth_key: vector<u8>) acquires Account { rotate_authentication_key_internal(account, new_auth_key);}
rotate_authentication_key_from_public_key
Private entry function for key rotation that allows the signer to update their authentication key from a given public key. This function will abort if the scheme is not recognized or if new_public_key_bytes is not a valid public key for the given scheme.
Note: This function does not update the OriginatingAddress
table.
entry fun rotate_authentication_key_from_public_key(account: &signer, scheme: u8, new_public_key_bytes: vector<u8>)
Implementation
entry fun rotate_authentication_key_from_public_key(account: &signer, scheme: u8, new_public_key_bytes: vector<u8>) acquires Account { let addr = signer::address_of(account); let account_resource = &Account[addr]; let old_auth_key = account_resource.authentication_key; let new_auth_key; if (scheme == ED25519_SCHEME) { let from_pk = ed25519::new_unvalidated_public_key_from_bytes(new_public_key_bytes); new_auth_key = ed25519::unvalidated_public_key_to_authentication_key(&from_pk); } else if (scheme == MULTI_ED25519_SCHEME) { let from_pk = multi_ed25519::new_unvalidated_public_key_from_bytes(new_public_key_bytes); new_auth_key = multi_ed25519::unvalidated_public_key_to_authentication_key(&from_pk); } else if (scheme == SINGLE_KEY_SCHEME) { new_auth_key = single_key::new_public_key_from_bytes(new_public_key_bytes).to_authentication_key(); } else if (scheme == MULTI_KEY_SCHEME) { new_auth_key = multi_key::new_public_key_from_bytes(new_public_key_bytes).to_authentication_key(); } else { abort error::invalid_argument(EUNRECOGNIZED_SCHEME) }; rotate_authentication_key_call(account, new_auth_key); event::emit(KeyRotationToPublicKey { account: addr, // Set verified_public_key_bit_map to [0x00, 0x00, 0x00, 0x00] as the public key(s) are not verified verified_public_key_bit_map: vector[0x00, 0x00, 0x00, 0x00], public_key_scheme: scheme, public_key: new_public_key_bytes, old_auth_key, new_auth_key, });}
upsert_ed25519_backup_key_on_keyless_account
Upserts an ED25519 backup key to an account that has a keyless public key as its original public key by converting the account’s authentication key to a multi-key of the original keyless public key and the new backup key that requires 1 signature from either key to authenticate. This function takes a the account’s original keyless public key and a ED25519 backup public key and rotates the account’s authentication key to a multi-key of the original keyless public key and the new backup key that requires 1 signature from either key to authenticate.
Note: This function emits a KeyRotationToMultiPublicKey
event marking both keys as verified since the keyless public key
is the original public key of the account and the new backup key has been validated via verifying the challenge signed by the new backup key.
Arguments
account
- The signer representing the keyless accountkeyless_public_key
- The original keyless public key of the account (wrapped in an AnyPublicKey)backup_public_key
- The ED25519 public key to add as a backupbackup_key_proof
- A signature from the backup key proving ownership
Aborts
- If the any of inputs deserialize incorrectly
- If the provided public key is not a keyless public key
- If the keyless public key is not the original public key of the account
- If the backup key proof signature is invalid
Events
- Emits a
KeyRotationToMultiPublicKey
event with the new multi-key configuration
entry fun upsert_ed25519_backup_key_on_keyless_account(account: &signer, keyless_public_key: vector<u8>, backup_public_key: vector<u8>, backup_key_proof: vector<u8>)
Implementation
entry fun upsert_ed25519_backup_key_on_keyless_account(account: &signer, keyless_public_key: vector<u8>, backup_public_key: vector<u8>, backup_key_proof: vector<u8>) acquires Account { // Check that the provided public key is a keyless public key let keyless_single_key = single_key::new_public_key_from_bytes(keyless_public_key); assert!(single_key::is_keyless_or_federated_keyless_public_key(&keyless_single_key), error::invalid_argument(ENOT_A_KEYLESS_PUBLIC_KEY));
let addr = signer::address_of(account); let account_resource = &mut Account[addr]; let old_auth_key = account_resource.authentication_key;
// Check that the provided public key is original public key of the account by comparing // its authentication key to the account address. assert!( bcs::to_bytes(&addr) == keyless_single_key.to_authentication_key(), error::invalid_argument(ENOT_THE_ORIGINAL_PUBLIC_KEY) );
let curr_auth_key_as_address = from_bcs::to_address(old_auth_key); let challenge = RotationProofChallenge { sequence_number: account_resource.sequence_number, originator: addr, current_auth_key: curr_auth_key_as_address, new_public_key: backup_public_key, };
// Assert the challenges signed by the provided backup key is valid assert_valid_rotation_proof_signature_and_get_auth_key( ED25519_SCHEME, backup_public_key, backup_key_proof, &challenge );
// Get the backup key as a single key let backup_key_ed25519 = ed25519::new_unvalidated_public_key_from_bytes(backup_public_key); let backup_key_as_single_key = single_key::from_ed25519_public_key_unvalidated(backup_key_ed25519);
let new_public_key = multi_key::new_multi_key_from_single_keys(vector[keyless_single_key, backup_key_as_single_key], 1); let new_auth_key = new_public_key.to_authentication_key();
// Rotate the authentication key to the new multi key public key rotate_authentication_key_call(account, new_auth_key);
event::emit(KeyRotationToPublicKey { account: addr, // This marks that both the keyless public key and the new backup key are verified // The keyless public key is the original public key of the account and the new backup key // has been validated via verifying the challenge signed by the new backup key. // Represents the bitmap 0b11000000000000000000000000000000 verified_public_key_bit_map: vector[0xC0, 0x00, 0x00, 0x00], public_key_scheme: MULTI_KEY_SCHEME, public_key: bcs::to_bytes(&new_public_key), old_auth_key, new_auth_key, });}
rotate_authentication_key
Generic authentication key rotation function that allows the user to rotate their authentication key from any scheme to any scheme. To authorize the rotation, we need two signatures:
- the first signature
cap_rotate_key
refers to the signature by the account owner’s current key on a validRotationProofChallenge
, demonstrating that the user intends to and has the capability to rotate the authentication key of this account; - the second signature
cap_update_table
refers to the signature by the new key (that the account owner wants to rotate to) on a validRotationProofChallenge
, demonstrating that the user owns the new private key, and has the authority to update theOriginatingAddress
map with the new address mapping<new_address, originating_address>
. To verify these two signatures, we need their corresponding public key and public key scheme: we usefrom_scheme
andfrom_public_key_bytes
to verifycap_rotate_key
, andto_scheme
andto_public_key_bytes
to verifycap_update_table
. A scheme of 0 refers to an Ed25519 key and a scheme of 1 refers to Multi-Ed25519 keys.originating address
refers to an account’s original/first address.
Here is an example attack if we don’t ask for the second signature cap_update_table
:
Alice has rotated her account addr_a
to new_addr_a
. As a result, the following entry is created, to help Alice when recovering her wallet:
OriginatingAddress[new_addr_a]
-> addr_a
Alice has had a bad day: her laptop blew up and she needs to reset her account on a new one.
(Fortunately, she still has her secret key new_sk_a
associated with her new address new_addr_a
, so she can do this.)
But Bob likes to mess with Alice.
Bob creates an account addr_b
and maliciously rotates it to Alice’s new address new_addr_a
. Since we are no longer checking a PoK,
Bob can easily do this.
Now, the table will be updated to make Alice’s new address point to Bob’s address: OriginatingAddress[new_addr_a]
-> addr_b
.
When Alice recovers her account, her wallet will display the attacker’s address (Bob’s) addr_b
as her address.
Now Alice will give addr_b
to everyone to pay her, but the money will go to Bob.
Because we ask for a valid cap_update_table
, this kind of attack is not possible. Bob would not have the secret key of Alice’s address
to rotate his address to Alice’s address in the first place.
public entry fun rotate_authentication_key(account: &signer, from_scheme: u8, from_public_key_bytes: vector<u8>, to_scheme: u8, to_public_key_bytes: vector<u8>, cap_rotate_key: vector<u8>, cap_update_table: vector<u8>)
Implementation
public entry fun rotate_authentication_key( account: &signer, from_scheme: u8, from_public_key_bytes: vector<u8>, to_scheme: u8, to_public_key_bytes: vector<u8>, cap_rotate_key: vector<u8>, cap_update_table: vector<u8>,) acquires Account, OriginatingAddress { let addr = signer::address_of(account); ensure_resource_exists(addr); check_rotation_permission(account); let account_resource = &mut Account[addr]; let old_auth_key = account_resource.authentication_key; // Verify the given `from_public_key_bytes` matches this account's current authentication key. if (from_scheme == ED25519_SCHEME) { let from_pk = ed25519::new_unvalidated_public_key_from_bytes(from_public_key_bytes); let from_auth_key = ed25519::unvalidated_public_key_to_authentication_key(&from_pk); assert!( account_resource.authentication_key == from_auth_key, error::unauthenticated(EWRONG_CURRENT_PUBLIC_KEY) ); } else if (from_scheme == MULTI_ED25519_SCHEME) { let from_pk = multi_ed25519::new_unvalidated_public_key_from_bytes(from_public_key_bytes); let from_auth_key = multi_ed25519::unvalidated_public_key_to_authentication_key(&from_pk); assert!( account_resource.authentication_key == from_auth_key, error::unauthenticated(EWRONG_CURRENT_PUBLIC_KEY) ); } else { abort error::invalid_argument(EINVALID_SCHEME) };
// Construct a valid `RotationProofChallenge` that `cap_rotate_key` and `cap_update_table` will validate against. let curr_auth_key_as_address = from_bcs::to_address(account_resource.authentication_key); let challenge = RotationProofChallenge { sequence_number: account_resource.sequence_number, originator: addr, current_auth_key: curr_auth_key_as_address, new_public_key: to_public_key_bytes, };
// Assert the challenges signed by the current and new keys are valid assert_valid_rotation_proof_signature_and_get_auth_key( from_scheme, from_public_key_bytes, cap_rotate_key, &challenge ); let new_auth_key = assert_valid_rotation_proof_signature_and_get_auth_key( to_scheme, to_public_key_bytes, cap_update_table, &challenge );
// Update the `OriginatingAddress` table. update_auth_key_and_originating_address_table(addr, account_resource, new_auth_key);
let verified_public_key_bit_map; if (to_scheme == ED25519_SCHEME) { // Set verified_public_key_bit_map to [0x80, 0x00, 0x00, 0x00] as the public key is verified and there is only one public key. verified_public_key_bit_map = vector[0x80, 0x00, 0x00, 0x00]; } else { // The new key is a multi-ed25519 key, so set the verified_public_key_bit_map to the signature bitmap. let len = vector::length(&cap_update_table); verified_public_key_bit_map = vector::slice(&cap_update_table, len - 4, len); };
event::emit(KeyRotationToPublicKey { account: addr, verified_public_key_bit_map, public_key_scheme: to_scheme, public_key: to_public_key_bytes, old_auth_key, new_auth_key, });}
rotate_authentication_key_with_rotation_capability
public entry fun rotate_authentication_key_with_rotation_capability(delegate_signer: &signer, rotation_cap_offerer_address: address, new_scheme: u8, new_public_key_bytes: vector<u8>, cap_update_table: vector<u8>)
Implementation
public entry fun rotate_authentication_key_with_rotation_capability( delegate_signer: &signer, rotation_cap_offerer_address: address, new_scheme: u8, new_public_key_bytes: vector<u8>, cap_update_table: vector<u8>) acquires Account, OriginatingAddress { check_rotation_permission(delegate_signer); assert!(resource_exists_at(rotation_cap_offerer_address), error::not_found(EOFFERER_ADDRESS_DOES_NOT_EXIST));
// Check that there exists a rotation capability offer at the offerer's account resource for the delegate. let delegate_address = signer::address_of(delegate_signer); let offerer_account_resource = &Account[rotation_cap_offerer_address]; let old_auth_key = offerer_account_resource.authentication_key; assert!( offerer_account_resource.rotation_capability_offer.for.contains(&delegate_address), error::not_found(ENO_SUCH_ROTATION_CAPABILITY_OFFER) );
let curr_auth_key = from_bcs::to_address(offerer_account_resource.authentication_key); let challenge = RotationProofChallenge { sequence_number: get_sequence_number(delegate_address), originator: rotation_cap_offerer_address, current_auth_key: curr_auth_key, new_public_key: new_public_key_bytes, };
// Verifies that the `RotationProofChallenge` from above is signed under the new public key that we are rotating to. l let new_auth_key = assert_valid_rotation_proof_signature_and_get_auth_key( new_scheme, new_public_key_bytes, cap_update_table, &challenge );
// Update the `OriginatingAddress` table, so we can find the originating address using the new address. let offerer_account_resource = &mut Account[rotation_cap_offerer_address]; update_auth_key_and_originating_address_table( rotation_cap_offerer_address, offerer_account_resource, new_auth_key );
let verified_public_key_bit_map; if (new_scheme == ED25519_SCHEME) { // Set verified_public_key_bit_map to [0x80, 0x00, 0x00, 0x00] as the public key is verified and there is only one public key. verified_public_key_bit_map = vector[0x80, 0x00, 0x00, 0x00]; } else { // The new key is a multi-ed25519 key, so set the verified_public_key_bit_map to the signature bitmap. let len = vector::length(&cap_update_table); verified_public_key_bit_map = vector::slice(&cap_update_table, len - 4, len); };
event::emit(KeyRotationToPublicKey { account: rotation_cap_offerer_address, verified_public_key_bit_map, public_key_scheme: new_scheme, public_key: new_public_key_bytes, old_auth_key, new_auth_key, });}
offer_rotation_capability
Offers rotation capability on behalf of account
to the account at address recipient_address
.
An account can delegate its rotation capability to only one other address at one time. If the account
has an existing rotation capability offer, calling this function will update the rotation capability offer with
the new recipient_address
.
Here, rotation_capability_sig_bytes
signature indicates that this key rotation is authorized by the account owner,
and prevents the classic “time-of-check time-of-use” attack.
For example, users usually rely on what the wallet displays to them as the transaction’s outcome. Consider a contract that with 50% probability
(based on the current timestamp in Move), rotates somebody’s key. The wallet might be unlucky and get an outcome where nothing is rotated,
incorrectly telling the user nothing bad will happen. But when the transaction actually gets executed, the attacker gets lucky and
the execution path triggers the account key rotation.
We prevent such attacks by asking for this extra signature authorizing the key rotation.
@param rotation_capability_sig_bytes is the signature by the account owner’s key on RotationCapabilityOfferProofChallengeV2
.
@param account_scheme is the scheme of the account (ed25519 or multi_ed25519).
@param account_public_key_bytes is the public key of the account owner.
@param recipient_address is the address of the recipient of the rotation capability - note that if there’s an existing rotation capability
offer, calling this function will replace the previous recipient_address
upon successful verification.
public entry fun offer_rotation_capability(account: &signer, rotation_capability_sig_bytes: vector<u8>, account_scheme: u8, account_public_key_bytes: vector<u8>, recipient_address: address)
Implementation
public entry fun offer_rotation_capability( account: &signer, rotation_capability_sig_bytes: vector<u8>, account_scheme: u8, account_public_key_bytes: vector<u8>, recipient_address: address,) acquires Account { check_rotation_permission(account); let addr = signer::address_of(account); ensure_resource_exists(addr); assert!(exists_at(recipient_address), error::not_found(EACCOUNT_DOES_NOT_EXIST));
// proof that this account intends to delegate its rotation capability to another account let account_resource = &mut Account[addr]; let proof_challenge = RotationCapabilityOfferProofChallengeV2 { chain_id: chain_id::get(), sequence_number: account_resource.sequence_number, source_address: addr, recipient_address, };
// verify the signature on `RotationCapabilityOfferProofChallengeV2` by the account owner if (account_scheme == ED25519_SCHEME) { let pubkey = ed25519::new_unvalidated_public_key_from_bytes(account_public_key_bytes); let expected_auth_key = ed25519::unvalidated_public_key_to_authentication_key(&pubkey); assert!( account_resource.authentication_key == expected_auth_key, error::invalid_argument(EWRONG_CURRENT_PUBLIC_KEY) );
let rotation_capability_sig = ed25519::new_signature_from_bytes(rotation_capability_sig_bytes); assert!( ed25519::signature_verify_strict_t(&rotation_capability_sig, &pubkey, proof_challenge), error::invalid_argument(EINVALID_PROOF_OF_KNOWLEDGE) ); } else if (account_scheme == MULTI_ED25519_SCHEME) { let pubkey = multi_ed25519::new_unvalidated_public_key_from_bytes(account_public_key_bytes); let expected_auth_key = multi_ed25519::unvalidated_public_key_to_authentication_key(&pubkey); assert!( account_resource.authentication_key == expected_auth_key, error::invalid_argument(EWRONG_CURRENT_PUBLIC_KEY) );
let rotation_capability_sig = multi_ed25519::new_signature_from_bytes(rotation_capability_sig_bytes); assert!( multi_ed25519::signature_verify_strict_t(&rotation_capability_sig, &pubkey, proof_challenge), error::invalid_argument(EINVALID_PROOF_OF_KNOWLEDGE) ); } else { abort error::invalid_argument(EINVALID_SCHEME) };
// update the existing rotation capability offer or put in a new rotation capability offer for the current account account_resource.rotation_capability_offer.for.swap_or_fill(recipient_address);}
set_originating_address
For the given account, add an entry to OriginatingAddress
table mapping the account’s
authentication key to the account’s address.
Can be used as a followup to rotate_authentication_key_call()
to reconcile the
OriginatingAddress
table, or to establish a mapping for a new account that has not yet had
its authentication key rotated.
Aborts if there is already an entry in the OriginatingAddress
table for the account’s
authentication key.
Kept as a private entry function to ensure that after an unproven rotation via
rotate_authentication_key_call()
, the OriginatingAddress
table is only updated under the
authority of the new authentication key.
entry fun set_originating_address(account: &signer)
Implementation
entry fun set_originating_address(account: &signer) acquires Account, OriginatingAddress { abort error::invalid_state(ESET_ORIGINATING_ADDRESS_DISABLED);
let account_addr = signer::address_of(account); assert!(exists<Account>(account_addr), error::not_found(EACCOUNT_DOES_NOT_EXIST)); let auth_key_as_address = from_bcs::to_address(Account[account_addr].authentication_key); let address_map_ref_mut = &mut OriginatingAddress[@aptos_framework].address_map; if (address_map_ref_mut.contains(auth_key_as_address)) { assert!( *address_map_ref_mut.borrow(auth_key_as_address) == account_addr, error::invalid_argument(ENEW_AUTH_KEY_ALREADY_MAPPED) ); } else { address_map_ref_mut.add(auth_key_as_address, account_addr); };}
is_rotation_capability_offered
Returns true if the account at account_addr
has a rotation capability offer.
#[view]public fun is_rotation_capability_offered(account_addr: address): bool
Implementation
public fun is_rotation_capability_offered(account_addr: address): bool acquires Account { if (features::is_default_account_resource_enabled()) { if (!resource_exists_at(account_addr)) { return false; } } else { assert!(exists_at(account_addr), error::not_found(EACCOUNT_DOES_NOT_EXIST)); }; let account_resource = &Account[account_addr]; account_resource.rotation_capability_offer.for.is_some()}
get_rotation_capability_offer_for
Returns the address of the account that has a rotation capability offer from the account at account_addr
.
#[view]public fun get_rotation_capability_offer_for(account_addr: address): address
Implementation
public fun get_rotation_capability_offer_for(account_addr: address): address acquires Account { assert_account_resource_with_error(account_addr, ENO_SUCH_ROTATION_CAPABILITY_OFFER); let account_resource = &Account[account_addr]; assert!( account_resource.rotation_capability_offer.for.is_some(), error::not_found(ENO_SIGNER_CAPABILITY_OFFERED), ); *account_resource.rotation_capability_offer.for.borrow()}
revoke_rotation_capability
Revoke the rotation capability offer given to to_be_revoked_recipient_address
from account
public entry fun revoke_rotation_capability(account: &signer, to_be_revoked_address: address)
Implementation
public entry fun revoke_rotation_capability(account: &signer, to_be_revoked_address: address) acquires Account { assert!(exists_at(to_be_revoked_address), error::not_found(EACCOUNT_DOES_NOT_EXIST)); check_rotation_permission(account); let addr = signer::address_of(account); assert_account_resource_with_error(addr, ENO_SUCH_ROTATION_CAPABILITY_OFFER); let account_resource = &Account[addr]; assert!( account_resource.rotation_capability_offer.for.contains(&to_be_revoked_address), error::not_found(ENO_SUCH_ROTATION_CAPABILITY_OFFER) ); revoke_any_rotation_capability(account);}
revoke_any_rotation_capability
Revoke any rotation capability offer in the specified account.
public entry fun revoke_any_rotation_capability(account: &signer)
Implementation
public entry fun revoke_any_rotation_capability(account: &signer) acquires Account { check_rotation_permission(account); let offerer_addr = signer::address_of(account); assert_account_resource_with_error(offerer_addr, ENO_SUCH_ROTATION_CAPABILITY_OFFER); let account_resource = &mut Account[signer::address_of(account)]; account_resource.rotation_capability_offer.for.extract();}
offer_signer_capability
Offers signer capability on behalf of account
to the account at address recipient_address
.
An account can delegate its signer capability to only one other address at one time.
signer_capability_key_bytes
is the SignerCapabilityOfferProofChallengeV2
signed by the account owner’s key
account_scheme
is the scheme of the account (ed25519 or multi_ed25519).
account_public_key_bytes
is the public key of the account owner.
recipient_address
is the address of the recipient of the signer capability - note that if there’s an existing
recipient_address
in the account owner’s SignerCapabilityOffer
, this will replace the
previous recipient_address
upon successful verification (the previous recipient will no longer have access
to the account owner’s signer capability).
public entry fun offer_signer_capability(account: &signer, signer_capability_sig_bytes: vector<u8>, account_scheme: u8, account_public_key_bytes: vector<u8>, recipient_address: address)
Implementation
public entry fun offer_signer_capability( account: &signer, signer_capability_sig_bytes: vector<u8>, account_scheme: u8, account_public_key_bytes: vector<u8>, recipient_address: address) acquires Account { check_offering_permission(account); let source_address = signer::address_of(account); ensure_resource_exists(source_address); assert!(exists_at(recipient_address), error::not_found(EACCOUNT_DOES_NOT_EXIST));
// Proof that this account intends to delegate its signer capability to another account. let proof_challenge = SignerCapabilityOfferProofChallengeV2 { sequence_number: get_sequence_number(source_address), source_address, recipient_address, }; verify_signed_message( source_address, account_scheme, account_public_key_bytes, signer_capability_sig_bytes, proof_challenge);
// Update the existing signer capability offer or put in a new signer capability offer for the recipient. let account_resource = &mut Account[source_address]; account_resource.signer_capability_offer.for.swap_or_fill(recipient_address);}
is_signer_capability_offered
Returns true if the account at account_addr
has a signer capability offer.
#[view]public fun is_signer_capability_offered(account_addr: address): bool
Implementation
public fun is_signer_capability_offered(account_addr: address): bool acquires Account { if (features::is_default_account_resource_enabled()) { if (!resource_exists_at(account_addr)) { return false; } } else { assert!(exists_at(account_addr), error::not_found(EACCOUNT_DOES_NOT_EXIST)); }; let account_resource = &Account[account_addr]; account_resource.signer_capability_offer.for.is_some()}
get_signer_capability_offer_for
Returns the address of the account that has a signer capability offer from the account at account_addr
.
#[view]public fun get_signer_capability_offer_for(account_addr: address): address
Implementation
public fun get_signer_capability_offer_for(account_addr: address): address acquires Account { assert_account_resource_with_error(account_addr, ENO_SIGNER_CAPABILITY_OFFERED); let account_resource = &Account[account_addr]; assert!( account_resource.signer_capability_offer.for.is_some(), error::not_found(ENO_SIGNER_CAPABILITY_OFFERED), ); *account_resource.signer_capability_offer.for.borrow()}
revoke_signer_capability
Revoke the account owner’s signer capability offer for to_be_revoked_address
(i.e., the address that
has a signer capability offer from account
but will be revoked in this function).
public entry fun revoke_signer_capability(account: &signer, to_be_revoked_address: address)
Implementation
public entry fun revoke_signer_capability(account: &signer, to_be_revoked_address: address) acquires Account { check_offering_permission(account); assert!(exists_at(to_be_revoked_address), error::not_found(EACCOUNT_DOES_NOT_EXIST)); let addr = signer::address_of(account); assert_account_resource_with_error(addr, ENO_SUCH_SIGNER_CAPABILITY); let account_resource = &Account[addr]; assert!( account_resource.signer_capability_offer.for.contains(&to_be_revoked_address), error::not_found(ENO_SUCH_SIGNER_CAPABILITY) ); revoke_any_signer_capability(account);}
revoke_any_signer_capability
Revoke any signer capability offer in the specified account.
public entry fun revoke_any_signer_capability(account: &signer)
Implementation
public entry fun revoke_any_signer_capability(account: &signer) acquires Account { check_offering_permission(account); let offerer_addr = signer::address_of(account); assert_account_resource_with_error(offerer_addr, ENO_SUCH_SIGNER_CAPABILITY); let account_resource = &mut Account[signer::address_of(account)]; account_resource.signer_capability_offer.for.extract();}
create_authorized_signer
Return an authorized signer of the offerer, if there’s an existing signer capability offer for account
at the offerer’s address.
public fun create_authorized_signer(account: &signer, offerer_address: address): signer
Implementation
public fun create_authorized_signer(account: &signer, offerer_address: address): signer acquires Account { check_offering_permission(account); assert_account_resource_with_error(offerer_address, ENO_SUCH_SIGNER_CAPABILITY); // Check if there's an existing signer capability offer from the offerer. let account_resource = &Account[offerer_address]; let addr = signer::address_of(account); assert!( account_resource.signer_capability_offer.for.contains(&addr), error::not_found(ENO_SUCH_SIGNER_CAPABILITY) );
create_signer(offerer_address)}
assert_account_resource_with_error
fun assert_account_resource_with_error(account: address, error_code: u64)
Implementation
inline fun assert_account_resource_with_error(account: address, error_code: u64) { if (features::is_default_account_resource_enabled()) { assert!( resource_exists_at(account), error::not_found(error_code), ); } else { assert!(exists_at(account), error::not_found(EACCOUNT_DOES_NOT_EXIST)); };}
assert_valid_rotation_proof_signature_and_get_auth_key
Helper functions for authentication key rotation.
fun assert_valid_rotation_proof_signature_and_get_auth_key(scheme: u8, public_key_bytes: vector<u8>, signature: vector<u8>, challenge: &account::RotationProofChallenge): vector<u8>
Implementation
fun assert_valid_rotation_proof_signature_and_get_auth_key( scheme: u8, public_key_bytes: vector<u8>, signature: vector<u8>, challenge: &RotationProofChallenge): vector<u8> { if (scheme == ED25519_SCHEME) { let pk = ed25519::new_unvalidated_public_key_from_bytes(public_key_bytes); let sig = ed25519::new_signature_from_bytes(signature); assert!( ed25519::signature_verify_strict_t(&sig, &pk, *challenge), std::error::invalid_argument(EINVALID_PROOF_OF_KNOWLEDGE) ); ed25519::unvalidated_public_key_to_authentication_key(&pk) } else if (scheme == MULTI_ED25519_SCHEME) { let pk = multi_ed25519::new_unvalidated_public_key_from_bytes(public_key_bytes); let sig = multi_ed25519::new_signature_from_bytes(signature); assert!( multi_ed25519::signature_verify_strict_t(&sig, &pk, *challenge), std::error::invalid_argument(EINVALID_PROOF_OF_KNOWLEDGE) ); multi_ed25519::unvalidated_public_key_to_authentication_key(&pk) } else { abort error::invalid_argument(EINVALID_SCHEME) }}
update_auth_key_and_originating_address_table
Update the OriginatingAddress
table, so that we can find the originating address using the latest address
in the event of key recovery.
fun update_auth_key_and_originating_address_table(originating_addr: address, account_resource: &mut account::Account, new_auth_key_vector: vector<u8>)
Implementation
fun update_auth_key_and_originating_address_table( originating_addr: address, account_resource: &mut Account, new_auth_key_vector: vector<u8>,) acquires OriginatingAddress { let address_map = &mut OriginatingAddress[@aptos_framework].address_map; let curr_auth_key = from_bcs::to_address(account_resource.authentication_key); let new_auth_key = from_bcs::to_address(new_auth_key_vector); assert!( new_auth_key != curr_auth_key, error::invalid_argument(ENEW_AUTH_KEY_SAME_AS_CURRENT) );
// Checks `OriginatingAddress[curr_auth_key]` is either unmapped, or mapped to `originating_address`. // If it's mapped to the originating address, removes that mapping. // Otherwise, abort if it's mapped to a different address. if (address_map.contains(curr_auth_key)) { // If account_a with address_a is rotating its keypair from keypair_a to keypair_b, we expect // the address of the account to stay the same, while its keypair updates to keypair_b. // Here, by asserting that we're calling from the account with the originating address, we enforce // the standard of keeping the same address and updating the keypair at the contract level. // Without this assertion, the dapps could also update the account's address to address_b (the address that // is programmatically related to keypaier_b) and update the keypair to keypair_b. This causes problems // for interoperability because different dapps can implement this in different ways. // If the account with address b calls this function with two valid signatures, it will abort at this step, // because address b is not the account's originating address. assert!( originating_addr == address_map.remove(curr_auth_key), error::not_found(EINVALID_ORIGINATING_ADDRESS) ); };
// Set `OriginatingAddress[new_auth_key] = originating_address`. assert!( !address_map.contains(new_auth_key), error::invalid_argument(ENEW_AUTH_KEY_ALREADY_MAPPED) ); address_map.add(new_auth_key, originating_addr);
if (std::features::module_event_migration_enabled()) { event::emit(KeyRotation { account: originating_addr, old_authentication_key: account_resource.authentication_key, new_authentication_key: new_auth_key_vector, }); } else { event::emit_event<KeyRotationEvent>( &mut account_resource.key_rotation_events, KeyRotationEvent { old_authentication_key: account_resource.authentication_key, new_authentication_key: new_auth_key_vector, } ); };
// Update the account resource's authentication key. account_resource.authentication_key = new_auth_key_vector;}
create_resource_address
Basic account creation methods. This is a helper function to compute resource addresses. Computation of the address involves the use of a cryptographic hash operation and should be use thoughtfully.
public fun create_resource_address(source: &address, seed: vector<u8>): address
Implementation
public fun create_resource_address(source: &address, seed: vector<u8>): address { let bytes = bcs::to_bytes(source); bytes.append(seed); bytes.push_back(DERIVE_RESOURCE_ACCOUNT_SCHEME); from_bcs::to_address(hash::sha3_256(bytes))}
create_resource_account
A resource account is used to manage resources independent of an account managed by a user.
In Aptos a resource account is created based upon the sha3 256 of the source’s address and additional seed data.
A resource account can only be created once, this is designated by setting the
Account::signer_capability_offer::for
to the address of the resource account. While an entity may call
create_account
to attempt to claim an account ahead of the creation of a resource account, if found Aptos will
transition ownership of the account over to the resource account. This is done by validating that the account has
yet to execute any transactions and that the Account::signer_capability_offer::for
is none. The probability of a
collision where someone has legitimately produced a private key that maps to a resource account address is less
than (1/2)^(256)
.
public fun create_resource_account(source: &signer, seed: vector<u8>): (signer, account::SignerCapability)
Implementation
public fun create_resource_account(source: &signer, seed: vector<u8>): (signer, SignerCapability) acquires Account { let resource_addr = create_resource_address(&signer::address_of(source), seed); let resource = if (exists_at(resource_addr)) { if (resource_exists_at(resource_addr)) { let account = &Account[resource_addr]; assert!( account.signer_capability_offer.for.is_none(), error::already_exists(ERESOURCE_ACCCOUNT_EXISTS), ); }; assert!( get_sequence_number(resource_addr) == 0, error::invalid_state(EACCOUNT_ALREADY_USED), ); create_signer(resource_addr) } else { create_account_unchecked(resource_addr) };
// By default, only the SignerCapability should have control over the resource account and not the auth key. // If the source account wants direct control via auth key, they would need to explicitly rotate the auth key // of the resource account using the SignerCapability. rotate_authentication_key_internal(&resource, ZERO_AUTH_KEY);
let account = &mut Account[resource_addr]; account.signer_capability_offer.for = option::some(resource_addr); let signer_cap = SignerCapability { account: resource_addr }; (resource, signer_cap)}
create_framework_reserved_account
create the account for system reserved addresses
public(friend) fun create_framework_reserved_account(addr: address): (signer, account::SignerCapability)
Implementation
public(friend) fun create_framework_reserved_account(addr: address): (signer, SignerCapability) { assert!( addr == @0x1 || addr == @0x2 || addr == @0x3 || addr == @0x4 || addr == @0x5 || addr == @0x6 || addr == @0x7 || addr == @0x8 || addr == @0x9 || addr == @0xa, error::permission_denied(ENO_VALID_FRAMEWORK_RESERVED_ADDRESS), ); let signer = create_account_unchecked(addr); let signer_cap = SignerCapability { account: addr }; (signer, signer_cap)}
create_guid
GUID management methods.
Creates a new GUID for account_signer
and increments the GUID creation number.
When the default_account_resource
feature flag is enabled:
- If no Account resource exists, one will be created automatically
- This ensures consistent GUID creation behavior for all addresses
When the feature flag is disabled:
- Aborts if no Account resource exists
Aborts if the maximum number of GUIDs has been reached (0x4000000000000)
public fun create_guid(account_signer: &signer): guid::GUID
Implementation
public fun create_guid(account_signer: &signer): guid::GUID acquires Account { let addr = signer::address_of(account_signer); ensure_resource_exists(addr); let account = &mut Account[addr]; let guid = guid::create(addr, &mut account.guid_creation_num); assert!( account.guid_creation_num < MAX_GUID_CREATION_NUM, error::out_of_range(EEXCEEDED_MAX_GUID_CREATION_NUM), ); guid}
new_event_handle
Creates a new event handle for account
.
This is a wrapper around create_guid
that creates an EventHandle,
inheriting the same behavior regarding account existence and feature flags.
public fun new_event_handle<T: drop, store>(account: &signer): event::EventHandle<T>
Implementation
public fun new_event_handle<T: drop + store>(account: &signer): EventHandle<T> acquires Account { event::new_event_handle(create_guid(account))}
register_coin
Coin management methods.
public(friend) fun register_coin<CoinType>(account_addr: address)
Implementation
public(friend) fun register_coin<CoinType>(account_addr: address) acquires Account { if (std::features::module_event_migration_enabled()) { event::emit( CoinRegister { account: account_addr, type_info: type_info::type_of<CoinType>(), }, ); } else { ensure_resource_exists(account_addr); let account = &mut Account[account_addr]; event::emit_event<CoinRegisterEvent>( &mut account.coin_register_events, CoinRegisterEvent { type_info: type_info::type_of<CoinType>(), }, ); }}
create_signer_with_capability
Capability based functions for efficient use.
public fun create_signer_with_capability(capability: &account::SignerCapability): signer
Implementation
public fun create_signer_with_capability(capability: &SignerCapability): signer { let addr = &capability.account; create_signer(*addr)}
get_signer_capability_address
public fun get_signer_capability_address(capability: &account::SignerCapability): address
Implementation
public fun get_signer_capability_address(capability: &SignerCapability): address { capability.account}
verify_signed_message
public fun verify_signed_message<T: drop>(account: address, account_scheme: u8, account_public_key: vector<u8>, signed_message_bytes: vector<u8>, message: T)
Implementation
public fun verify_signed_message<T: drop>( account: address, account_scheme: u8, account_public_key: vector<u8>, signed_message_bytes: vector<u8>, message: T,) acquires Account { let auth_key = get_authentication_key(account); // Verify that the `SignerCapabilityOfferProofChallengeV2` has the right information and is signed by the account owner's key if (account_scheme == ED25519_SCHEME) { let pubkey = ed25519::new_unvalidated_public_key_from_bytes(account_public_key); let expected_auth_key = ed25519::unvalidated_public_key_to_authentication_key(&pubkey); assert!( auth_key == expected_auth_key, error::invalid_argument(EWRONG_CURRENT_PUBLIC_KEY), );
let signer_capability_sig = ed25519::new_signature_from_bytes(signed_message_bytes); assert!( ed25519::signature_verify_strict_t(&signer_capability_sig, &pubkey, message), error::invalid_argument(EINVALID_PROOF_OF_KNOWLEDGE), ); } else if (account_scheme == MULTI_ED25519_SCHEME) { let pubkey = multi_ed25519::new_unvalidated_public_key_from_bytes(account_public_key); let expected_auth_key = multi_ed25519::unvalidated_public_key_to_authentication_key(&pubkey); assert!( auth_key == expected_auth_key, error::invalid_argument(EWRONG_CURRENT_PUBLIC_KEY), );
let signer_capability_sig = multi_ed25519::new_signature_from_bytes(signed_message_bytes); assert!( multi_ed25519::signature_verify_strict_t(&signer_capability_sig, &pubkey, message), error::invalid_argument(EINVALID_PROOF_OF_KNOWLEDGE), ); } else { abort error::invalid_argument(EINVALID_SCHEME) };}
Specification
High-level Requirements
No. | Requirement | Criticality | Implementation | Enforcement |
---|---|---|---|---|
1 | The initialization of the account module should result in the proper system initialization with valid and consistent resources. | High | Initialization of the account module creates a valid address_map table and moves the resources to the OriginatingAddress under the aptos_framework account. | Audited that the address_map table is created and populated correctly with the expected initial values. |
2 | After successfully creating an account, the account resources should initialize with the default data, ensuring the proper initialization of the account state. | High | Creating an account via the create_account function validates the state and moves a new account resource under new_address. | Formally verified via create_account. |
3 | Checking the existence of an account under a given address never results in an abort. | Low | The exists_at function returns a boolean value indicating the existence of an account under the given address. | Formally verified by the aborts_if condition. |
4 | The account module maintains bounded sequence numbers for all accounts, guaranteeing they remain within the specified limit. | Medium | The sequence number of an account may only increase up to MAX_U64 in a succeeding manner. | Formally verified via increment_sequence_number that it remains within the defined boundary of MAX_U64. |
5 | Only the ed25519 and multied25519 signature schemes are permissible. | Low | Exclusively perform key rotation using either the ed25519 or multied25519 signature schemes. Currently restricts the offering of rotation/signing capabilities to the ed25519 or multied25519 schemes. | Formally Verified: rotate_authentication_key, offer_rotation_capability, and offer_signer_capability. Verified that it aborts if the account_scheme is not ED25519_SCHEME and not MULTI_ED25519_SCHEME. Audited that the scheme enums correspond correctly to signature logic. |
6 | Exclusively permit the rotation of the authentication key of an account for the account owner or any user who possesses rotation capabilities associated with that account. | Critical | In the rotate_authentication_key function, the authentication key derived from the from_public_key_bytes should match the signer's current authentication key. Only the delegate_signer granted the rotation capabilities may invoke the rotate_authentication_key_with_rotation_capability function. | Formally Verified via rotate_authentication_key and rotate_authentication_key_with_rotation_capability. |
7 | Only the owner of an account may offer or revoke the following capabilities: (1) offer_rotation_capability, (2) offer_signer_capability, (3) revoke_rotation_capability, and (4) revoke_signer_capability. | Critical | An account resource may only be modified by the owner of the account utilizing: rotation_capability_offer, signer_capability_offer. | Formally verified via offer_rotation_capability, offer_signer_capability, and revoke_rotation_capability. and revoke_signer_capability. |
8 | The capability to create a signer for the account is exclusively reserved for either the account owner or the account that has been granted the signing capabilities. | Critical | Signer creation for the account may only be successfully executed by explicitly granting the signing capabilities with the create_authorized_signer function. | Formally verified via create_authorized_signer. |
9 | Rotating the authentication key requires two valid signatures. With the private key of the current authentication key. With the private key of the new authentication key. | Critical | The rotate_authentication_key verifies two signatures (current and new) before rotating to the new key. The first signature ensures the user has the intended capability, and the second signature ensures that the user owns the new key. | Formally verified via rotate_authentication_key and rotate_authentication_key_with_rotation_capability. |
10 | The rotation of the authentication key updates the account's authentication key with the newly supplied one. | High | The auth_key may only update to the provided new_auth_key after verifying the signature. | Formally Verified in rotate_authentication_key_internal that the authentication key of an account is modified to the provided authentication key if the signature verification was successful. |
11 | The creation number is monotonically increasing. | Low | The guid_creation_num in the Account structure is monotonically increasing. | Formally Verified via guid_creation_num. |
12 | The Account resource is persistent. | Low | The Account structure assigned to the address should be persistent. | Audited that the Account structure is persistent. |
Module-level Specification
pragma verify = false;
initialize
public(friend) fun initialize(aptos_framework: &signer)
Only the address @aptos_framework
can call.
OriginatingAddress does not exist under @aptos_framework
before the call.
let aptos_addr = signer::address_of(aptos_framework);aborts_if !system_addresses::is_aptos_framework_address(aptos_addr);aborts_if exists<OriginatingAddress>(aptos_addr);ensures exists<OriginatingAddress>(aptos_addr);
create_account_if_does_not_exist
public fun create_account_if_does_not_exist(account_address: address)
Ensure that the account exists at the end of the call.
let authentication_key = bcs::to_bytes(account_address);modifies global<Account>(account_address);aborts_if !exists<Account>(account_address) && ( account_address == @vm_reserved || account_address == @aptos_framework || account_address == @aptos_token || !(len(authentication_key) == 32));ensures exists<Account>(account_address);
create_account
public(friend) fun create_account(new_address: address): signer
Check if the bytes of the new address is 32. The Account does not exist under the new address before creating the account. Limit the new account address is not @vm_reserved / @aptos_framework / @aptos_toke.
include CreateAccountAbortsIf {addr: new_address};aborts_if new_address == @vm_reserved || new_address == @aptos_framework || new_address == @aptos_token;ensures signer::address_of(result) == new_address;// This enforces high-level requirement 2:ensures exists<Account>(new_address);
create_account_unchecked
fun create_account_unchecked(new_address: address): signer
Check if the bytes of the new address is 32. The Account does not exist under the new address before creating the account.
pragma opaque;include CreateAccountAbortsIf {addr: new_address};modifies global<Account>(new_address);ensures signer::address_of(result) == new_address;ensures exists<Account>(new_address);
exists_at
#[view]public fun exists_at(addr: address): bool
pragma opaque;// This enforces high-level requirement 3:aborts_if false;ensures result == spec_exists_at(addr);
fun spec_exists_at(addr: address): bool { use std::features; use std::features::DEFAULT_ACCOUNT_RESOURCE; features::spec_is_enabled(DEFAULT_ACCOUNT_RESOURCE) || exists<Account>(addr)}
schema CreateAccountAbortsIf { addr: address; let authentication_key = bcs::to_bytes(addr); aborts_if len(authentication_key) != 32; aborts_if exists<Account>(addr); ensures len(authentication_key) == 32;}
get_guid_next_creation_num
#[view]public fun get_guid_next_creation_num(addr: address): u64
aborts_if !exists<Account>(addr);ensures result == global<Account>(addr).guid_creation_num;
get_sequence_number
#[view]public fun get_sequence_number(addr: address): u64
aborts_if !exists<Account>(addr);ensures result == global<Account>(addr).sequence_number;
originating_address
#[view]public fun originating_address(auth_key: address): option::Option<address>
pragma verify=false;
increment_sequence_number
public(friend) fun increment_sequence_number(addr: address)
The Account existed under the address. The sequence_number of the Account is up to MAX_U64.
let sequence_number = global<Account>(addr).sequence_number;aborts_if !exists<Account>(addr);// This enforces high-level requirement 4:aborts_if sequence_number == MAX_U64;modifies global<Account>(addr);let post post_sequence_number = global<Account>(addr).sequence_number;ensures post_sequence_number == sequence_number + 1;
get_authentication_key
#[view]public fun get_authentication_key(addr: address): vector<u8>
pragma opaque;aborts_if !exists<Account>(addr);ensures result == spec_get_authentication_key(addr);
fun spec_get_authentication_key(addr: address): vector<u8> { global<Account>(addr).authentication_key}
rotate_authentication_key_internal
public(friend) fun rotate_authentication_key_internal(account: &signer, new_auth_key: vector<u8>)
The Account existed under the signer before the call. The length of new_auth_key is 32.
let addr = signer::address_of(account);// This enforces high-level requirement 10:let post account_resource = global<Account>(addr);aborts_if !exists<Account>(addr);aborts_if vector::length(new_auth_key) != 32;modifies global<Account>(addr);ensures account_resource.authentication_key == new_auth_key;
rotate_authentication_key_call
entry fun rotate_authentication_key_call(account: &signer, new_auth_key: vector<u8>)
let addr = signer::address_of(account);// This enforces high-level requirement 10:let post account_resource = global<Account>(addr);aborts_if !exists<Account>(addr);aborts_if vector::length(new_auth_key) != 32;modifies global<Account>(addr);ensures account_resource.authentication_key == new_auth_key;
rotate_authentication_key_from_public_key
entry fun rotate_authentication_key_from_public_key(account: &signer, scheme: u8, new_public_key_bytes: vector<u8>)
aborts_if scheme != ED25519_SCHEME && scheme != MULTI_ED25519_SCHEME && scheme != SINGLE_KEY_SCHEME && scheme != MULTI_KEY_SCHEME;
fun spec_assert_valid_rotation_proof_signature_and_get_auth_key(scheme: u8, public_key_bytes: vector<u8>, signature: vector<u8>, challenge: RotationProofChallenge): vector<u8>;
rotate_authentication_key
public entry fun rotate_authentication_key(account: &signer, from_scheme: u8, from_public_key_bytes: vector<u8>, to_scheme: u8, to_public_key_bytes: vector<u8>, cap_rotate_key: vector<u8>, cap_update_table: vector<u8>)
The Account existed under the signer The authentication scheme is ED25519_SCHEME and MULTI_ED25519_SCHEME
let addr = signer::address_of(account);let account_resource = global<Account>(addr);aborts_if !exists<Account>(addr);// This enforces high-level requirement 6:include from_scheme == ED25519_SCHEME ==> ed25519::NewUnvalidatedPublicKeyFromBytesAbortsIf { bytes: from_public_key_bytes };aborts_if from_scheme == ED25519_SCHEME && ({ let expected_auth_key = ed25519::spec_public_key_bytes_to_authentication_key(from_public_key_bytes); account_resource.authentication_key != expected_auth_key});include from_scheme == MULTI_ED25519_SCHEME ==> multi_ed25519::NewUnvalidatedPublicKeyFromBytesAbortsIf { bytes: from_public_key_bytes };aborts_if from_scheme == MULTI_ED25519_SCHEME && ({ let from_auth_key = multi_ed25519::spec_public_key_bytes_to_authentication_key(from_public_key_bytes); account_resource.authentication_key != from_auth_key});// This enforces high-level requirement 5:aborts_if from_scheme != ED25519_SCHEME && from_scheme != MULTI_ED25519_SCHEME;let curr_auth_key = from_bcs::deserialize<address>(account_resource.authentication_key);aborts_if !from_bcs::deserializable<address>(account_resource.authentication_key);let challenge = RotationProofChallenge { sequence_number: account_resource.sequence_number, originator: addr, current_auth_key: curr_auth_key, new_public_key: to_public_key_bytes,};// This enforces high-level requirement 9:include AssertValidRotationProofSignatureAndGetAuthKeyAbortsIf { scheme: from_scheme, public_key_bytes: from_public_key_bytes, signature: cap_rotate_key, challenge,};include AssertValidRotationProofSignatureAndGetAuthKeyAbortsIf { scheme: to_scheme, public_key_bytes: to_public_key_bytes, signature: cap_update_table, challenge,};let originating_addr = addr;let new_auth_key_vector = spec_assert_valid_rotation_proof_signature_and_get_auth_key(to_scheme, to_public_key_bytes, cap_update_table, challenge);let address_map = global<OriginatingAddress>(@aptos_framework).address_map;let new_auth_key = from_bcs::deserialize<address>(new_auth_key_vector);aborts_if !exists<OriginatingAddress>(@aptos_framework);aborts_if !from_bcs::deserializable<address>(account_resource.authentication_key);aborts_if table::spec_contains(address_map, curr_auth_key) && table::spec_get(address_map, curr_auth_key) != originating_addr;aborts_if !from_bcs::deserializable<address>(new_auth_key_vector);aborts_if curr_auth_key != new_auth_key && table::spec_contains(address_map, new_auth_key);include UpdateAuthKeyAndOriginatingAddressTableAbortsIf { originating_addr: addr,};let post auth_key = global<Account>(addr).authentication_key;ensures auth_key == new_auth_key_vector;
rotate_authentication_key_with_rotation_capability
public entry fun rotate_authentication_key_with_rotation_capability(delegate_signer: &signer, rotation_cap_offerer_address: address, new_scheme: u8, new_public_key_bytes: vector<u8>, cap_update_table: vector<u8>)
aborts_if !exists<Account>(rotation_cap_offerer_address);let delegate_address = signer::address_of(delegate_signer);let offerer_account_resource = global<Account>(rotation_cap_offerer_address);aborts_if !from_bcs::deserializable<address>(offerer_account_resource.authentication_key);let curr_auth_key = from_bcs::deserialize<address>(offerer_account_resource.authentication_key);aborts_if !exists<Account>(delegate_address);let challenge = RotationProofChallenge { sequence_number: global<Account>(delegate_address).sequence_number, originator: rotation_cap_offerer_address, current_auth_key: curr_auth_key, new_public_key: new_public_key_bytes,};// This enforces high-level requirement 6:aborts_if !option::spec_contains(offerer_account_resource.rotation_capability_offer.for, delegate_address);// This enforces high-level requirement 9:include AssertValidRotationProofSignatureAndGetAuthKeyAbortsIf { scheme: new_scheme, public_key_bytes: new_public_key_bytes, signature: cap_update_table, challenge,};let new_auth_key_vector = spec_assert_valid_rotation_proof_signature_and_get_auth_key(new_scheme, new_public_key_bytes, cap_update_table, challenge);let address_map = global<OriginatingAddress>(@aptos_framework).address_map;aborts_if !exists<OriginatingAddress>(@aptos_framework);aborts_if !from_bcs::deserializable<address>(offerer_account_resource.authentication_key);aborts_if table::spec_contains(address_map, curr_auth_key) && table::spec_get(address_map, curr_auth_key) != rotation_cap_offerer_address;aborts_if !from_bcs::deserializable<address>(new_auth_key_vector);let new_auth_key = from_bcs::deserialize<address>(new_auth_key_vector);aborts_if curr_auth_key != new_auth_key && table::spec_contains(address_map, new_auth_key);include UpdateAuthKeyAndOriginatingAddressTableAbortsIf { originating_addr: rotation_cap_offerer_address, account_resource: offerer_account_resource,};let post auth_key = global<Account>(rotation_cap_offerer_address).authentication_key;ensures auth_key == new_auth_key_vector;
offer_rotation_capability
public entry fun offer_rotation_capability(account: &signer, rotation_capability_sig_bytes: vector<u8>, account_scheme: u8, account_public_key_bytes: vector<u8>, recipient_address: address)
let source_address = signer::address_of(account);let account_resource = global<Account>(source_address);let proof_challenge = RotationCapabilityOfferProofChallengeV2 { chain_id: global<chain_id::ChainId>(@aptos_framework).id, sequence_number: account_resource.sequence_number, source_address, recipient_address,};aborts_if !exists<chain_id::ChainId>(@aptos_framework);aborts_if !exists<Account>(recipient_address);aborts_if !exists<Account>(source_address);include account_scheme == ED25519_SCHEME ==> ed25519::NewUnvalidatedPublicKeyFromBytesAbortsIf { bytes: account_public_key_bytes };aborts_if account_scheme == ED25519_SCHEME && ({ let expected_auth_key = ed25519::spec_public_key_bytes_to_authentication_key(account_public_key_bytes); account_resource.authentication_key != expected_auth_key});include account_scheme == ED25519_SCHEME ==> ed25519::NewSignatureFromBytesAbortsIf { bytes: rotation_capability_sig_bytes };aborts_if account_scheme == ED25519_SCHEME && !ed25519::spec_signature_verify_strict_t( ed25519::Signature { bytes: rotation_capability_sig_bytes }, ed25519::UnvalidatedPublicKey { bytes: account_public_key_bytes }, proof_challenge);include account_scheme == MULTI_ED25519_SCHEME ==> multi_ed25519::NewUnvalidatedPublicKeyFromBytesAbortsIf { bytes: account_public_key_bytes };aborts_if account_scheme == MULTI_ED25519_SCHEME && ({ let expected_auth_key = multi_ed25519::spec_public_key_bytes_to_authentication_key(account_public_key_bytes); account_resource.authentication_key != expected_auth_key});include account_scheme == MULTI_ED25519_SCHEME ==> multi_ed25519::NewSignatureFromBytesAbortsIf { bytes: rotation_capability_sig_bytes };aborts_if account_scheme == MULTI_ED25519_SCHEME && !multi_ed25519::spec_signature_verify_strict_t( multi_ed25519::Signature { bytes: rotation_capability_sig_bytes }, multi_ed25519::UnvalidatedPublicKey { bytes: account_public_key_bytes }, proof_challenge);// This enforces high-level requirement 5:aborts_if account_scheme != ED25519_SCHEME && account_scheme != MULTI_ED25519_SCHEME;// This enforces high-level requirement 7:modifies global<Account>(source_address);let post offer_for = global<Account>(source_address).rotation_capability_offer.for;ensures option::spec_borrow(offer_for) == recipient_address;
set_originating_address
entry fun set_originating_address(account: &signer)
pragma verify=false;
is_rotation_capability_offered
#[view]public fun is_rotation_capability_offered(account_addr: address): bool
aborts_if !exists<Account>(account_addr);
get_rotation_capability_offer_for
#[view]public fun get_rotation_capability_offer_for(account_addr: address): address
aborts_if !exists<Account>(account_addr);let account_resource = global<Account>(account_addr);aborts_if len(account_resource.rotation_capability_offer.for.vec) == 0;
revoke_rotation_capability
public entry fun revoke_rotation_capability(account: &signer, to_be_revoked_address: address)
aborts_if !exists<Account>(to_be_revoked_address);let addr = signer::address_of(account);let account_resource = global<Account>(addr);aborts_if !exists<Account>(addr);aborts_if !option::spec_contains(account_resource.rotation_capability_offer.for,to_be_revoked_address);modifies global<Account>(addr);ensures exists<Account>(to_be_revoked_address);let post offer_for = global<Account>(addr).rotation_capability_offer.for;ensures !option::spec_is_some(offer_for);
revoke_any_rotation_capability
public entry fun revoke_any_rotation_capability(account: &signer)
let addr = signer::address_of(account);modifies global<Account>(addr);aborts_if !exists<Account>(addr);let account_resource = global<Account>(addr);// This enforces high-level requirement 7:aborts_if !option::is_some(account_resource.rotation_capability_offer.for);let post offer_for = global<Account>(addr).rotation_capability_offer.for;ensures !option::spec_is_some(offer_for);
offer_signer_capability
public entry fun offer_signer_capability(account: &signer, signer_capability_sig_bytes: vector<u8>, account_scheme: u8, account_public_key_bytes: vector<u8>, recipient_address: address)
The Account existed under the signer. The authentication scheme is ED25519_SCHEME and MULTI_ED25519_SCHEME.
let source_address = signer::address_of(account);let account_resource = global<Account>(source_address);let proof_challenge = SignerCapabilityOfferProofChallengeV2 { sequence_number: account_resource.sequence_number, source_address, recipient_address,};aborts_if !exists<Account>(recipient_address);aborts_if !exists<Account>(source_address);include account_scheme == ED25519_SCHEME ==> ed25519::NewUnvalidatedPublicKeyFromBytesAbortsIf { bytes: account_public_key_bytes };aborts_if account_scheme == ED25519_SCHEME && ({ let expected_auth_key = ed25519::spec_public_key_bytes_to_authentication_key(account_public_key_bytes); account_resource.authentication_key != expected_auth_key});include account_scheme == ED25519_SCHEME ==> ed25519::NewSignatureFromBytesAbortsIf { bytes: signer_capability_sig_bytes };aborts_if account_scheme == ED25519_SCHEME && !ed25519::spec_signature_verify_strict_t( ed25519::Signature { bytes: signer_capability_sig_bytes }, ed25519::UnvalidatedPublicKey { bytes: account_public_key_bytes }, proof_challenge);include account_scheme == MULTI_ED25519_SCHEME ==> multi_ed25519::NewUnvalidatedPublicKeyFromBytesAbortsIf { bytes: account_public_key_bytes };aborts_if account_scheme == MULTI_ED25519_SCHEME && ({ let expected_auth_key = multi_ed25519::spec_public_key_bytes_to_authentication_key(account_public_key_bytes); account_resource.authentication_key != expected_auth_key});include account_scheme == MULTI_ED25519_SCHEME ==> multi_ed25519::NewSignatureFromBytesAbortsIf { bytes: signer_capability_sig_bytes };aborts_if account_scheme == MULTI_ED25519_SCHEME && !multi_ed25519::spec_signature_verify_strict_t( multi_ed25519::Signature { bytes: signer_capability_sig_bytes }, multi_ed25519::UnvalidatedPublicKey { bytes: account_public_key_bytes }, proof_challenge);// This enforces high-level requirement 5:aborts_if account_scheme != ED25519_SCHEME && account_scheme != MULTI_ED25519_SCHEME;// This enforces high-level requirement 7:modifies global<Account>(source_address);let post offer_for = global<Account>(source_address).signer_capability_offer.for;ensures option::spec_borrow(offer_for) == recipient_address;
is_signer_capability_offered
#[view]public fun is_signer_capability_offered(account_addr: address): bool
aborts_if !exists<Account>(account_addr);
get_signer_capability_offer_for
#[view]public fun get_signer_capability_offer_for(account_addr: address): address
aborts_if !exists<Account>(account_addr);let account_resource = global<Account>(account_addr);aborts_if len(account_resource.signer_capability_offer.for.vec) == 0;
revoke_signer_capability
public entry fun revoke_signer_capability(account: &signer, to_be_revoked_address: address)
The Account existed under the signer. The value of signer_capability_offer.for of Account resource under the signer is to_be_revoked_address.
aborts_if !exists<Account>(to_be_revoked_address);let addr = signer::address_of(account);let account_resource = global<Account>(addr);aborts_if !exists<Account>(addr);aborts_if !option::spec_contains(account_resource.signer_capability_offer.for,to_be_revoked_address);modifies global<Account>(addr);ensures exists<Account>(to_be_revoked_address);
revoke_any_signer_capability
public entry fun revoke_any_signer_capability(account: &signer)
modifies global<Account>(signer::address_of(account));// This enforces high-level requirement 7:aborts_if !exists<Account>(signer::address_of(account));let account_resource = global<Account>(signer::address_of(account));aborts_if !option::is_some(account_resource.signer_capability_offer.for);
create_authorized_signer
public fun create_authorized_signer(account: &signer, offerer_address: address): signer
The Account existed under the signer. The value of signer_capability_offer.for of Account resource under the signer is offerer_address.
// This enforces high-level requirement 8:include AccountContainsAddr{ account, address: offerer_address,};modifies global<Account>(offerer_address);ensures exists<Account>(offerer_address);ensures signer::address_of(result) == offerer_address;
schema AccountContainsAddr { account: signer; address: address; let addr = signer::address_of(account); let account_resource = global<Account>(address); aborts_if !exists<Account>(address); // This enforces high-level requirement 3 of the create_signer module: aborts_if !option::spec_contains(account_resource.signer_capability_offer.for,addr);}
assert_valid_rotation_proof_signature_and_get_auth_key
fun assert_valid_rotation_proof_signature_and_get_auth_key(scheme: u8, public_key_bytes: vector<u8>, signature: vector<u8>, challenge: &account::RotationProofChallenge): vector<u8>
pragma opaque;include AssertValidRotationProofSignatureAndGetAuthKeyAbortsIf;ensures [abstract] result == spec_assert_valid_rotation_proof_signature_and_get_auth_key(scheme, public_key_bytes, signature, challenge);
schema AssertValidRotationProofSignatureAndGetAuthKeyAbortsIf { scheme: u8; public_key_bytes: vector<u8>; signature: vector<u8>; challenge: RotationProofChallenge; include scheme == ED25519_SCHEME ==> ed25519::NewUnvalidatedPublicKeyFromBytesAbortsIf { bytes: public_key_bytes }; include scheme == ED25519_SCHEME ==> ed25519::NewSignatureFromBytesAbortsIf { bytes: signature }; aborts_if scheme == ED25519_SCHEME && !ed25519::spec_signature_verify_strict_t( ed25519::Signature { bytes: signature }, ed25519::UnvalidatedPublicKey { bytes: public_key_bytes }, challenge ); include scheme == MULTI_ED25519_SCHEME ==> multi_ed25519::NewUnvalidatedPublicKeyFromBytesAbortsIf { bytes: public_key_bytes }; include scheme == MULTI_ED25519_SCHEME ==> multi_ed25519::NewSignatureFromBytesAbortsIf { bytes: signature }; aborts_if scheme == MULTI_ED25519_SCHEME && !multi_ed25519::spec_signature_verify_strict_t( multi_ed25519::Signature { bytes: signature }, multi_ed25519::UnvalidatedPublicKey { bytes: public_key_bytes }, challenge ); aborts_if scheme != ED25519_SCHEME && scheme != MULTI_ED25519_SCHEME;}
update_auth_key_and_originating_address_table
fun update_auth_key_and_originating_address_table(originating_addr: address, account_resource: &mut account::Account, new_auth_key_vector: vector<u8>)
modifies global<OriginatingAddress>(@aptos_framework);include UpdateAuthKeyAndOriginatingAddressTableAbortsIf;
schema UpdateAuthKeyAndOriginatingAddressTableAbortsIf { originating_addr: address; account_resource: Account; new_auth_key_vector: vector<u8>; let address_map = global<OriginatingAddress>(@aptos_framework).address_map; let curr_auth_key = from_bcs::deserialize<address>(account_resource.authentication_key); let new_auth_key = from_bcs::deserialize<address>(new_auth_key_vector); aborts_if !exists<OriginatingAddress>(@aptos_framework); aborts_if !from_bcs::deserializable<address>(account_resource.authentication_key); aborts_if table::spec_contains(address_map, curr_auth_key) && table::spec_get(address_map, curr_auth_key) != originating_addr; aborts_if !from_bcs::deserializable<address>(new_auth_key_vector); aborts_if curr_auth_key == new_auth_key; aborts_if curr_auth_key != new_auth_key && table::spec_contains(address_map, new_auth_key); ensures table::spec_contains(global<OriginatingAddress>(@aptos_framework).address_map, from_bcs::deserialize<address>(new_auth_key_vector));}
create_resource_address
public fun create_resource_address(source: &address, seed: vector<u8>): address
The Account existed under the signer The value of signer_capability_offer.for of Account resource under the signer is to_be_revoked_address
pragma opaque;pragma aborts_if_is_strict = false;aborts_if [abstract] false;ensures [abstract] result == spec_create_resource_address(source, seed);ensures [abstract] source != result;
fun spec_create_resource_address(source: address, seed: vector<u8>): address;
create_resource_account
public fun create_resource_account(source: &signer, seed: vector<u8>): (signer, account::SignerCapability)
let source_addr = signer::address_of(source);let resource_addr = spec_create_resource_address(source_addr, seed);aborts_if len(ZERO_AUTH_KEY) != 32;include spec_exists_at(resource_addr) ==> CreateResourceAccountAbortsIf;include !spec_exists_at(resource_addr) ==> CreateAccountAbortsIf {addr: resource_addr};ensures signer::address_of(result_1) == resource_addr;let post offer_for = global<Account>(resource_addr).signer_capability_offer.for;ensures option::spec_borrow(offer_for) == resource_addr;ensures result_2 == SignerCapability { account: resource_addr };
create_framework_reserved_account
public(friend) fun create_framework_reserved_account(addr: address): (signer, account::SignerCapability)
Check if the bytes of the new address is 32. The Account does not exist under the new address before creating the account. The system reserved addresses is @0x1 / @0x2 / @0x3 / @0x4 / @0x5 / @0x6 / @0x7 / @0x8 / @0x9 / @0xa.
aborts_if spec_is_framework_address(addr);include CreateAccountAbortsIf {addr};ensures signer::address_of(result_1) == addr;ensures result_2 == SignerCapability { account: addr };
fun spec_is_framework_address(addr: address): bool{ addr != @0x1 && addr != @0x2 && addr != @0x3 && addr != @0x4 && addr != @0x5 && addr != @0x6 && addr != @0x7 && addr != @0x8 && addr != @0x9 && addr != @0xa}
create_guid
public fun create_guid(account_signer: &signer): guid::GUID
The Account existed under the signer. The guid_creation_num of the account resource is up to MAX_U64.
let addr = signer::address_of(account_signer);include NewEventHandleAbortsIf { account: account_signer,};modifies global<Account>(addr);// This enforces high-level requirement 11:ensures global<Account>(addr).guid_creation_num == old(global<Account>(addr).guid_creation_num) + 1;
new_event_handle
public fun new_event_handle<T: drop, store>(account: &signer): event::EventHandle<T>
The Account existed under the signer. The guid_creation_num of the Account is up to MAX_U64.
include NewEventHandleAbortsIf;
schema NewEventHandleAbortsIf { account: &signer; let addr = signer::address_of(account); let account = global<Account>(addr); aborts_if !exists<Account>(addr); aborts_if account.guid_creation_num + 1 > MAX_U64; aborts_if account.guid_creation_num + 1 >= MAX_GUID_CREATION_NUM;}
register_coin
public(friend) fun register_coin<CoinType>(account_addr: address)
aborts_if !exists<Account>(account_addr);aborts_if !type_info::spec_is_struct<CoinType>();modifies global<Account>(account_addr);
create_signer_with_capability
public fun create_signer_with_capability(capability: &account::SignerCapability): signer
let addr = capability.account;ensures signer::address_of(result) == addr;
schema CreateResourceAccountAbortsIf { resource_addr: address; let account = global<Account>(resource_addr);}
verify_signed_message
public fun verify_signed_message<T: drop>(account: address, account_scheme: u8, account_public_key: vector<u8>, signed_message_bytes: vector<u8>, message: T)
pragma aborts_if_is_partial;modifies global<Account>(account);let account_resource = global<Account>(account);aborts_if !exists<Account>(account);include account_scheme == ED25519_SCHEME ==> ed25519::NewUnvalidatedPublicKeyFromBytesAbortsIf { bytes: account_public_key };aborts_if account_scheme == ED25519_SCHEME && ({ let expected_auth_key = ed25519::spec_public_key_bytes_to_authentication_key(account_public_key); account_resource.authentication_key != expected_auth_key});include account_scheme == MULTI_ED25519_SCHEME ==> multi_ed25519::NewUnvalidatedPublicKeyFromBytesAbortsIf { bytes: account_public_key };aborts_if account_scheme == MULTI_ED25519_SCHEME && ({ let expected_auth_key = multi_ed25519::spec_public_key_bytes_to_authentication_key(account_public_key); account_resource.authentication_key != expected_auth_key});include account_scheme == ED25519_SCHEME ==> ed25519::NewSignatureFromBytesAbortsIf { bytes: signed_message_bytes };include account_scheme == MULTI_ED25519_SCHEME ==> multi_ed25519::NewSignatureFromBytesAbortsIf { bytes: signed_message_bytes };aborts_if account_scheme != ED25519_SCHEME && account_scheme != MULTI_ED25519_SCHEME;
Enum AccountPermission
enum AccountPermission has copy, drop, store