Aptos Key Rotation | Learning Move 0x02

NonceGeekDAO
11 min readJul 10, 2023

--

Author Introduction:

Ashley is a blockchain security researcher with passion for exploring new challenges and staying up-to-date with the latest technological advancements. In addition, she is also a Solidity and Move developer with experience in building DeFi protocols and conducting vulnerability mining.

Twitter: https://twitter.com/ashleyhsu_eth

Move Learning Camp Link:

https://github.com/NonceGeek/Web3-dApp-Camp

In this article, we will delve into the concept of Aptos key rotation and its significance. Before we proceed, it is essential to understand the concept of an Aptos account and the roles played by public and private keys. When a private key is compromised, the account becomes vulnerable to attacks. However, to maintain the original account and ensure asset security, the implementation of key rotation technology becomes crucial. Aptos accounts are designed to support key rotation, enabling users to change their private key without the need to create a new account. This process allows for the preservation of assets and the identity associated with the original account. In the following sections, we will introduce how to utilize the Aptos CLI and Python SDK to execute key rotation effectively, ensuring the ongoing safety of your account assets and identity.

0x01 What is an account?

Each Aptos account represents an entity on the blockchain that can send transactions and is identified by a specific 32-byte account address. This account is a container for Move modules and Move resources, and can contain blockchain assets such as coins and NFTs. These assets are represented as resources in blockchain accounts, and accounts can also perform various operations, such as sending resources to other people. Just like in the Web2 world, your email address represents your email account, and in the Aptos blockchain, the account address represents the account itself, and you can use this address to send and receive assets and other operations.

1.1 public key and private key

The private key on the Aptos blockchain, like other blockchains, can be used to sign transactions to be recognized and verified, just like in real life a signature or password is required to transfer money. Only signed transactions can make changes to your account assets, but only those who have the private key can sign the transaction, so the private key is a very important and sensitive set of data.

On other blockchains, your public identity is the public key corresponding to the private key, the address can be deduced from the public key, and the public key is also used to verify the signature. However, Aptos supports key rotation, which results in the public key being subject to change, so addresses are used to represent accounts. Traditionally, each account has a specific address as its identifier, and this address will not change. In Aptos, even if you change the authentication key (which contains the public key), the account address remains unique and does not change.

1.2 authentication key

The Aptos blockchain supports single-signature and multi-signature accounts. Multi-signature accounts allow multiple users to jointly execute digital signatures to manage account assets. For example, imagine a safe with two locks and two keys, one held by Alice and the other by Bob. The only way to open this safe is if two people provide keys to unlock the lock at the same time, and it cannot be opened with only one of the keys. A multi-signature account is similar to this. It is a single account representing multiple parties. All transactions that occur require the signatures of all parties involved. A threshold can also be set. For example, 2-of-3 means that only two of the three A signature completes the transaction.

A multi-signature account uses multiple (private key, public key) key pairs, no single public key will represent the account. Therefore, we need a key representing this account in order to encapsulate the public keys of all users in the multi-signature account. This key representing this account is also called an authentication key.

Authentication keys are a way of representing all users in a multi-signature account. In short, authentication keys are created by concatenating the concatenated hashes of all participating users’ public keys.

auth_key = sha3-256(pubkey_1 | . . . | pubkey_n | K | 0x01)

Where K is the signature threshold required to verify the transaction, and 0x01 is the 1-byte multi-signature scheme identifier.

For single-signature accounts, there is also an authentication key. The authentication key of a single-signature account only encapsulates a public key to represent the account. This is done to maintain consistency across all types of accounts.

auth_key = sha3-256(pubkey_A | 0x00)

Where 0x00 is a 1-byte single-signature scheme identifier.

We could say that an authentication key is a generalized public representation of a private key.

1.3 account address

During the account creation process, after a set of public and private keys are generated, a 32-byte authentication key will be calculated, which will be used as the account address.

Single sign account:

auth_key = sha3-256(pubkey_A | 0x00)

Where 0x00 is a 1-byte single-signature scheme identifier.

Multi-signature account:

auth_key = sha3-256(pubkey_1 | . . . | pubkey_n | K | 0x01)

Where K is the signature threshold required to verify the transaction, and 0x01 is the 1-byte multi-signature scheme identifier.

However, after key rotation, the authentication key changes, and when a new public-private key pair is generated, the public key will be rotated key. However, the account address will not change. Therefore, only initially, the 32-byte authentication key will be the same as the 32-byte account address. The decoupling of accounts and keys enables Aptos to seamlessly add new digital signature algorithms to support both public and private key types.

Once an account is created, the account address will maintain the same, although the private key, public key, and authentication key may change.

0x02 Why do we need key rotation?

When the private key is leaked, your account can be compromised. In web2, most people will change their passwords regularly to reduce the risk of being stolen, and after your Instagram or Facebook account is stolen, you can verify your identity through other methods, change your password and get your account back. However, in most blockchains, the situation is not that simple. Private keys come in pairs with public keys, and on-chain identities are bound to private keys, and the only solution is to create a new account, use the new public and private key pair, and transfer all assets into that account.

However, this method will cause many problems, such as losing the original account assets, OG status , and commemoration of the activities participated in, etc. Therefore, “key rotation” came into being. Aptos accounts support key rotation, allowing you to change your private key without having to create a new account, thereby preserving the assets and identity of the original account. This means you can keep your original address and others can still send you assets using your original address. The only thing that will change is the authentication key, since it is computed from the public key.

The key rotation technology realizes the separation of on-chain identity and security. When the private key is leaked, you can replace the private key to protect assets from loss or theft. In addition, key rotation also allows us to change keys regularly, reducing the risk of private keys being stolen, and also provides more flexibility for using multi-signature accounts.

0x03 Authentication key rotation

Key rotation, exactly called Authentication key rotation in Aptos.

account.move module contains all Aptos account related function. Users can achieve key rotation through account::rotate_authentication_key function.

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 {

...
}

In order to authorize the rotation, we need two signatures:

  1. cap_rotate_key refers to the signature of RotationProofChallenge by the current owner of the account, proving that the user intends and has the ability to rotate the authentication key of this account
  2. cap_update_table refers to the signature of the new key that needs to be rotated to (the key that the account owner wants to rotate) to RotationProofChallenge, proving that the user has the new private key and has the right to update the OriginatingAddress mapping with New address mapping <new_address, originating_address>.

To verify these two signatures, we need their corresponding public keys and public key schemes: we use from_scheme and from_public_key_bytes to verify cap_rotate_key, and to_scheme and to_public_key_bytes to verify cap_update_table.

Single signature is ED25519_SCHEME, multi-signature is MULTI_ED25519_SCHEME.

3.1 Use Aptos cli to achieve single-sign account key rotation

In the initial state, you can see that the account address is the same as the authentication key.

  1. Generate a key:
aptos key generate --key-type ed25519 --output-file output.key
{
"Result": {
"PrivateKey Path": "output.key",
"PublicKey Path": "output.key.pub"
}
}

2. Use aptos cli to do key rotation, the parameter can choose to directly fill in the private key or key file:

aptos account rotate-key --new-private-key-file output.key
aptos account rotate-key --new-private-key 0xae249782eedbfafcc7da157542d1f97d97385cbda36338c806475a3ce127fd90

Do you want to submit a transaction for a range of [52100 - 78100] Octas at a gas unit price of 100 Octas? [yes/no] >
yes
{
"transaction_hash": "0x75c412d82e038f87cec14c6c8b08c7fb08290118437e18433700c0e5d0262854",
"gas_used": 521,
"gas_unit_price": 100,
"sender": "148f1f6f88d690a04451fa8a548f21129b537cf511cedaa66a6ad93abc83a607",
"sequence_number": 0,
"success": true,
"timestamp_us": 1688636116823320,
"version": 571106848,
"vm_status": "Executed successfully"
}
Do you want to create a profile for the new key? [yes/no] >
yes
Enter the name for the profile
update
Profile Update is saved.
{
"Result": {
"message": "Profile Update is saved.",
"transaction": {
"transaction_hash": "0x75c412d82e038f87cec14c6c8b08c7fb08290118437e18433700c0e5d0262854",
"gas_used": 521,
"gas_unit_price": 100,
"sender": "148f1f6f88d690a04451fa8a548f21129b537cf511cedaa66a6ad93abc83a607",
"sequence_number": 0,
"success": true,
"timestamp_us": 1688636116823320,
"version": 571106848,
"vm_status": "Executed successfully"
}
}
}

3. Go back to Aptos explorer to check, the authentication key has changed, but the account address remains the same.

The aptos cli can also check the account address through the public key:

aptos account lookup-address --public-key-file output.key.pub
{
"Result": "148f1f6f88d690a04451fa8a548f21129b537cf511cedaa66a6ad93abc83a607"
}

3.2 Use the Python SDK to rotate a single-signature account to a multi-signature account

Take multisig example of python sdk in aptos-core as a reference, you can follow Modify it for your own use case.

To summarize the process:

  1. Create a single sign account
  2. Create a multi-signature account, here is to create a 2-of-3 multi-signature account
  3. Sign the rotation proof challenge
  4. Execute the rotation authentication key

3.3.1 Environment installation

Install Poetry

Poetry is recommended for Python package management.

  1. Install Poetry
curl -sSL https://install.python-poetry.org | python3 -

2. Add Poetry to PATH According to your own system environment, add to .zshrc or .bashrc

export PATH="$HOME/.local/bin:$PATH"

3. Confirm Poetry installation is complete

poetry --version

Install Aptos Python SDK

pip3 install aptos-sdk

For more detailed information, please refer to Official Documentation.

3.3.2 Execute the Aptos SDK Example

git clone https://github.com/aptos-labs/aptos-core.git
cd aptos-core/ecosystem/python/sdk
  • Reproduce the project’s Poetry virtual environment
poetry env use python3
  • Mounting kit
poetry install
  • implement
poetry run python -m examples.multisig

set up

Import the library to be used

from aptos_sdk.account import Account, RotationProofChallenge
from aptos_sdk.account_address import AccountAddress
from aptos_sdk.authenticator import Authenticator, MultiEd25519Authenticator
from aptos_sdk.bcs import Serializer
from aptos_sdk.client import FaucetClient, RestClient
from aptos_sdk.ed25519 import MultiPublicKey, MultiSignature
from aptos_sdk.transactions import (
EntryFunction,
RawTransaction,
Script,
ScriptArgument,
Signed Transaction,
TransactionArgument,
Transaction Payload,
)
from aptos_sdk.type_tag import StructTag, TypeTag

Set the connection node and faucet information, here use the devnet environment:

NODE_URL = os.getenv("APTOS_NODE_URL", "https://fullnode.devnet.aptoslabs.com/v1")
FAUCET_URL = os.getenv(
"APTOS_FAUCET_URL",
"https://faucet.devnet.aptoslabs.com",
)

Initialize the client

rest_client = RestClient(NODE_URL)
faucet_client = FaucetClient(FAUCET_URL, rest_client)

Create a single sign account

deedee = Account. generate()
faucet_client.fund_account(deedee.address(), 50_000_000)
deedee_balance = rest_client.account_balance(deedee.address())
Deedee's address: 0x7ee730f9bb87e78b0f51b4399113af2ed6bc50d0a8e0bc27f914c287af6a9d49
Deedee's auth key: 0x7ee730f9bb87e78b0f51b4399113af2ed6bc50d0a8e0bc27f914c287af6a9d49
Deedee's public key: 0x641cb41098c6cea2c2643508ee28e116459c666e353d6dc708290c5dd8f27169
Deedee's balance: 50000000

Create a multi-signature account

Here we choose to create a 2-of-3 multi-signature account, so first create three single-signature accounts, so that there will be three pairs of public and private keys.

Here we choose to create a 2-of-3 multi-signature account, so first create three single-signature accounts, so that there will be three pairs of public and private keys.

# generate account
alice = Account. generate()
bob = Account. generate()
chad = Account. generate()

# fund account
faucet_client.fund_account(alice.address(), 10_000_000)
faucet_client.fund_account(bob.address(), 20_000_000)
faucet_client.fund_account(chad.address(), 30_000_000)
=== Account addresses ===
Alice: 0xe2f0ac3bf3066f83c3f13df40eb1f1e980b15bbf6198dfee0d4bfd741baf92a8
Bob: 0x55234e90dec96a74938a4da2f7815b97494fc3c4b4d8782fa9c0e83399613310
Chad: 0x9b2ff4d016b1dead4c79487275bcb332ce56d0c707bc07cf0f15e223e64122a0

=== Authentication keys ===
Alice: 0xe2f0ac3bf3066f83c3f13df40eb1f1e980b15bbf6198dfee0d4bfd741baf92a8
Bob: 0x55234e90dec96a74938a4da2f7815b97494fc3c4b4d8782fa9c0e83399613310
Chad: 0x9b2ff4d016b1dead4c79487275bcb332ce56d0c707bc07cf0f15e223e64122a0

=== Public keys ===
Alice: 0x53a03cc129319935ddabc8ac2afb0c5e320cd5bda347413fb6fe33bcdd0b7fb3
Bob: 0x51a95405c021b0449d11d71797ecb72e931c045a55f69ae96aa6c87d55ba8098
Chad: 0x74ac180a4c5b4f6e14f13b8a5e6dd19d102c0e3543268816dcb6484f0da5444d

Next, use the public key generated in the previous step to generate a 2-of-3 multi-signature account public key and address.

# generate public key from Alice, Bob and Chad's public key
threshold = 2
multisig_public_key = MultiPublicKey(
[alice.public_key(), bob.public_key(), chad.public_key()], threshold
)
# get address
multisig_address = AccountAddress.from_multi_ed25519(multisig_public_key)
# fund account
faucet_client.fund_account(multisig_address, 40_000_000)
=== 2-of-3 Multisig account ===
Account public key: 2-of-3 Multi-Ed25519 public key
Account address: 0x77e7728ef2189e48b3b898087c460a63f14955bcc6e4915b95aab8f864be8d05

Sign the rotation proof challenge

Before calling rotate_authentication_key, you need to prepare the incoming parameters, namely cap_rotate_key and cap_update_table. cap_rotate_key table Deedee approves this authentication key rotation. cap_update_table verifies that multisig accounts approve authentication key rotation.

rotation_proof_challenge = RotationProofChallenge(
sequence_number=0,
originator = deedee. address(),
current_auth_key = deedee. address(),
new_public_key=multisig_public_key.to_bytes(),
)

serserializer = Serializer()
rotation_proof_challenge. serialize(serializer)
rotation_proof_challenge_bcs = serializer. output()

cap_rotate_key = deedee.sign(rotation_proof_challenge_bcs).data()

cap_update_table = MultiSignature(
multisig_public_key,
[
(bob. public_key(), bob. sign(rotation_proof_challenge_bcs)),
(chad. public_key(), chad. sign(rotation_proof_challenge_bcs)),
],
).to_bytes()
=== Signing rotation proof challenge ===
cap_rotate_key: 0x497d25f09c29df422e620dd8eaf44ee9b7bbc2844b5143ade652d9a0d084cd1b59e66e6d0c8ebd3a4e6d5b70ddc0f635bbe321ea6ac2902d2ab07e3248 e5ac08
cap_update_table: 0xe40f69589e89beca67362c2cadb145df92d77351bfe77aa9318af88f1453576b2fc8e8723fca961e696014a0077946a762c18d7a64547b3ec1f053 9f88889303d89de34be00ada58151a124cc3610226ca1c0ca9814be9040bba3c6d02f169a53b922ce6cb026165746c8e90837affac8692206dc9eb0f82f0117742d33167 0a60000000

Execute rotation authentication key

Transactions for auth key rotation can now be submitted. Once executed, the rotated authentication key matches the address of the multisig account.

from_scheme = Authenticator.ED25519
from_public_key_bytes = deedee.public_key().key.encode()
to_scheme = Authenticator.MULTI_ED25519
to_public_key_bytes = multisig_public_key.to_bytes()

entry_function = EntryFunction. natural(
module="0x1::account",
function="rotate_authentication_key",
ty_args=[],
args=[
TransactionArgument(from_scheme, Serializer.u8),
TransactionArgument(from_public_key_bytes, Serializer.to_bytes),
TransactionArgument(to_scheme, Serializer.u8),
TransactionArgument(to_public_key_bytes, Serializer.to_bytes),
TransactionArgument(cap_rotate_key, Serializer.to_bytes),
TransactionArgument(cap_update_table, Serializer.to_bytes),
],
)

signed_transaction = rest_client.create_bcs_signed_transaction(
deedee, TransactionPayload(entry_function)
)

# auth key before key rotation
auth_key = rest_client.account(deedee.address())["authentication_key"]
print(f"Auth key pre-rotation: {auth_key}")

# send key rotation tx
tx_hash = rest_client.submit_bcs_transaction(signed_transaction)
rest_client.wait_for_transaction(tx_hash)
print(f"Transaction hash: {tx_hash}")

# auth key after key rotation
auth_key = rest_client.account(deedee.address())["authentication_key"]
print(f"New auth key: {auth_key}")
print(f"1st multisig address: {multisig_address}")
=== Submitting authentication key rotation transaction ===
Auth key pre-rotation: 0x7ee730f9bb87e78b0f51b4399113af2ed6bc50d0a8e0bc27f914c287af6a9d49
Transaction hash: 0x16046f72559268dc4025d28bbc2d8c91ab7a780e237cbf430965477910014df0
New auth key: 0x77e7728ef2189e48b3b898087c460a63f14955bcc6e4915b95aab8f864be8d05
1st multisig address: 0x77e7728ef2189e48b3b898087c460a63f14955bcc6e4915b95aab8f864be8d05

0x04 Summarize

The Aptos account decouples the address identity on the chain from the private key, provides single-signature and multi-signature accounts, and most importantly has the function of key rotation. The address remains the same after account creation, and remains the same even after key rotation. Key rotation changes the public-private key pair as well as the authentication key.

0x05 Reference

--

--

NonceGeekDAO
NonceGeekDAO

Written by NonceGeekDAO

Technical Articles of NonceGeekDAO.

Responses (1)