diff --git a/api/src/tests/objects.rs b/api/src/tests/objects.rs
index 8c0ab0dab5ef7..d6d6ee6bcb0e2 100644
--- a/api/src/tests/objects.rs
+++ b/api/src/tests/objects.rs
@@ -25,7 +25,7 @@ async fn test_gen_object() {
let user_addr = user.address();
// Publish packages
- let named_addresses = vec![("token_objects".to_string(), user_addr)];
+ let named_addresses = vec![("hero".to_string(), user_addr)];
let txn = futures::executor::block_on(async move {
let path = PathBuf::from(std::env!("CARGO_MANIFEST_DIR"))
.join("../aptos-move/move-examples/token_objects/hero");
diff --git a/aptos-move/e2e-move-tests/src/tests/token_objects.rs b/aptos-move/e2e-move-tests/src/tests/token_objects.rs
index c80f9ffea7d17..6c6f8554f31b6 100644
--- a/aptos-move/e2e-move-tests/src/tests/token_objects.rs
+++ b/aptos-move/e2e-move-tests/src/tests/token_objects.rs
@@ -37,7 +37,7 @@ fn test_basic_token() {
let mut build_options = aptos_framework::BuildOptions::default();
build_options
.named_addresses
- .insert("token_objects".to_string(), addr);
+ .insert("hero".to_string(), addr);
let result = h.publish_package_with_options(
&account,
diff --git a/aptos-move/framework/aptos-framework/doc/event.md b/aptos-move/framework/aptos-framework/doc/event.md
index 01a27f8f03082..e223b12853dc3 100644
--- a/aptos-move/framework/aptos-framework/doc/event.md
+++ b/aptos-move/framework/aptos-framework/doc/event.md
@@ -246,7 +246,7 @@ Destroy a unique handle.
pragma opaque;
aborts_if [abstract] false;
-ensures handle_ref.counter < MAX_U64 ==> handle_ref.counter == old(handle_ref.counter) + 1;
+ensures [concrete] handle_ref.counter == old(handle_ref.counter) + 1;
diff --git a/aptos-move/framework/aptos-framework/sources/event.spec.move b/aptos-move/framework/aptos-framework/sources/event.spec.move
index 29510c02e2f69..e6a26f7206671 100644
--- a/aptos-move/framework/aptos-framework/sources/event.spec.move
+++ b/aptos-move/framework/aptos-framework/sources/event.spec.move
@@ -7,7 +7,7 @@ spec aptos_framework::event {
spec emit_event {
pragma opaque;
aborts_if [abstract] false;
- ensures handle_ref.counter < MAX_U64 ==> handle_ref.counter == old(handle_ref.counter) + 1;
+ ensures [concrete] handle_ref.counter == old(handle_ref.counter) + 1;
}
/// Native function use opaque.
diff --git a/aptos-move/move-examples/swap/Move.toml b/aptos-move/move-examples/swap/Move.toml
index 824f5d3c89114..796c503e40f6a 100644
--- a/aptos-move/move-examples/swap/Move.toml
+++ b/aptos-move/move-examples/swap/Move.toml
@@ -1,5 +1,5 @@
[package]
-name = "CoinWrapper"
+name = "swap"
version = "0.0.0"
[addresses]
diff --git a/aptos-move/move-examples/tests/move_unit_tests.rs b/aptos-move/move-examples/tests/move_unit_tests.rs
index 5137c30e376a7..038efd4de6894 100644
--- a/aptos-move/move-examples/tests/move_unit_tests.rs
+++ b/aptos-move/move-examples/tests/move_unit_tests.rs
@@ -194,13 +194,28 @@ fn test_shared_account() {
#[test]
fn test_token_objects() {
- let named_address = BTreeMap::from([(
- String::from("token_objects"),
- AccountAddress::from_hex_literal("0xcafe").unwrap(),
- )]);
- run_tests_for_pkg("token_objects/hero", named_address.clone());
- run_tests_for_pkg("token_objects/token_lockup", named_address.clone());
- run_tests_for_pkg("token_objects/ambassador/move", named_address);
+ let named_addresses = BTreeMap::from([
+ (
+ String::from("ambassador"),
+ AccountAddress::from_hex_literal("0xcafe").unwrap(),
+ ),
+ (
+ String::from("hero"),
+ AccountAddress::from_hex_literal("0xcafe").unwrap(),
+ ),
+ (
+ String::from("knight"),
+ AccountAddress::from_hex_literal("0xcafe").unwrap(),
+ ),
+ (
+ String::from("token_lockup"),
+ AccountAddress::from_hex_literal("0xcafe").unwrap(),
+ ),
+ ]);
+ run_tests_for_pkg("token_objects/ambassador", named_addresses.clone());
+ run_tests_for_pkg("token_objects/hero", named_addresses.clone());
+ run_tests_for_pkg("token_objects/knight", named_addresses.clone());
+ run_tests_for_pkg("token_objects/token_lockup", named_addresses);
}
#[test]
diff --git a/aptos-move/move-examples/token_objects/ambassador/Move.toml b/aptos-move/move-examples/token_objects/ambassador/Move.toml
index 7a3cded854dab..12f7cb000a15b 100644
--- a/aptos-move/move-examples/token_objects/ambassador/Move.toml
+++ b/aptos-move/move-examples/token_objects/ambassador/Move.toml
@@ -1,9 +1,9 @@
[package]
-name = 'ambassador_token'
+name = 'ambassador'
version = '1.0.0'
[addresses]
-token_objects = "0xCAFE"
+ambassador = "_"
[dependencies]
AptosFramework = { local = "../../../framework/aptos-framework" }
diff --git a/aptos-move/move-examples/token_objects/ambassador/sources/ambassador.move b/aptos-move/move-examples/token_objects/ambassador/sources/ambassador.move
index d60102db45134..87d85d4dab2bd 100644
--- a/aptos-move/move-examples/token_objects/ambassador/sources/ambassador.move
+++ b/aptos-move/move-examples/token_objects/ambassador/sources/ambassador.move
@@ -10,7 +10,7 @@
/// The rank is stored in the property map, thus displayed in a wallet as a trait of the token.
/// The token uri is the concatenation of the base uri and the rank, where the base uri is given
/// as an argument of the minting function. So, the token uri changes when the rank changes.
-module token_objects::ambassador {
+module ambassador::ambassador {
use std::error;
use std::option;
use std::string::{Self, String};
diff --git a/aptos-move/move-examples/token_objects/hero/Move.toml b/aptos-move/move-examples/token_objects/hero/Move.toml
index 841b82bdce54a..14561d2ba61d7 100644
--- a/aptos-move/move-examples/token_objects/hero/Move.toml
+++ b/aptos-move/move-examples/token_objects/hero/Move.toml
@@ -1,9 +1,9 @@
[package]
-name = "TokenObjects"
+name = "Hero"
version = "0.0.0"
[addresses]
-token_objects = "_"
+hero = "_"
[dependencies]
AptosFramework = { local = "../../../framework/aptos-framework" }
diff --git a/aptos-move/move-examples/token_objects/hero/sources/hero.move b/aptos-move/move-examples/token_objects/hero/sources/hero.move
index 2947bc28314e5..1fcaddd2318c4 100644
--- a/aptos-move/move-examples/token_objects/hero/sources/hero.move
+++ b/aptos-move/move-examples/token_objects/hero/sources/hero.move
@@ -1,4 +1,4 @@
-module token_objects::hero {
+module hero::hero {
use std::error;
use std::option::{Self, Option};
use std::signer;
diff --git a/aptos-move/move-examples/token_objects/knight/Move.toml b/aptos-move/move-examples/token_objects/knight/Move.toml
index 04e829911b84a..0328e4adb3e80 100644
--- a/aptos-move/move-examples/token_objects/knight/Move.toml
+++ b/aptos-move/move-examples/token_objects/knight/Move.toml
@@ -3,7 +3,7 @@ name = 'knight'
version = '1.0.0'
[addresses]
-token_objects = "_"
+knight = "_"
[dependencies]
AptosFramework = { local = "../../../framework/aptos-framework" }
diff --git a/aptos-move/move-examples/token_objects/knight/sources/food.move b/aptos-move/move-examples/token_objects/knight/sources/food.move
index 2a79fe4542743..b6e2cc70275d1 100644
--- a/aptos-move/move-examples/token_objects/knight/sources/food.move
+++ b/aptos-move/move-examples/token_objects/knight/sources/food.move
@@ -1,16 +1,18 @@
/// This module implements the the food tokens (fungible token). When the module initializes,
/// it creates the collection and two fungible tokens such as Corn and Meat.
-module token_objects::food {
- use std::error;
- use std::option;
- use std::signer;
- use std::string::{Self, String};
+module knight::food {
use aptos_framework::fungible_asset::{Self, Metadata};
use aptos_framework::object::{Self, Object};
use aptos_framework::primary_fungible_store;
use aptos_token_objects::collection;
use aptos_token_objects::property_map;
use aptos_token_objects::token;
+ use std::error;
+ use std::option;
+ use std::signer;
+ use std::string::{Self, String};
+
+ friend knight::knight;
/// The token does not exist
const ETOKEN_DOES_NOT_EXIST: u64 = 1;
@@ -53,8 +55,6 @@ module token_objects::food {
const CONDITION_HUNGRY: vector = b"Hungry";
const CONDITION_GOOD: vector = b"Good";
- friend token_objects::knight;
-
#[resource_group_member(group = aptos_framework::object::ObjectGroup)]
// Food Token
struct FoodToken has key {
@@ -131,7 +131,7 @@ module token_objects::food {
#[view]
/// Returns the food token address by name
public fun food_token_address(food_token_name: String): address {
- token::create_token_address(&@token_objects, &string::utf8(FOOD_COLLECTION_NAME), &food_token_name)
+ token::create_token_address(&@knight, &string::utf8(FOOD_COLLECTION_NAME), &food_token_name)
}
/// Mints the given amount of the corn token to the given receiver.
@@ -277,10 +277,10 @@ module token_objects::food {
init_module(creator);
}
- #[test(creator = @token_objects, user1 = @0x456, user2 = @0x789)]
+ #[test(creator = @knight, user1 = @0x456, user2 = @0x789)]
public fun test_food(creator: &signer, user1: &signer, user2: &signer) acquires FoodToken {
- // This test assumes that the creator's address is equal to @token_objects.
- assert!(signer::address_of(creator) == @token_objects, 0);
+ // This test assumes that the creator's address is equal to @knight.
+ assert!(signer::address_of(creator) == @knight, 0);
// ---------------------------------------------------------------------
// Creator creates the collection, and mints corn and meat tokens in it.
diff --git a/aptos-move/move-examples/token_objects/knight/sources/knight.move b/aptos-move/move-examples/token_objects/knight/sources/knight.move
index f7561446ddc7e..20d53c1751f37 100644
--- a/aptos-move/move-examples/token_objects/knight/sources/knight.move
+++ b/aptos-move/move-examples/token_objects/knight/sources/knight.move
@@ -1,15 +1,16 @@
/// This module implements the knight token (non-fungible token) including the
/// functions create the collection and the knight tokens, and the function to feed a
/// knight token with food tokens to increase the knight's health point.
-module token_objects::knight {
- use std::option;
- use std::string::{Self, String};
+module knight::knight {
use aptos_framework::event;
use aptos_framework::object::{Self, Object};
use aptos_token_objects::collection;
use aptos_token_objects::property_map;
use aptos_token_objects::token;
- use token_objects::food::{Self, FoodToken};
+ use std::option;
+ use std::signer;
+ use std::string::{Self, String};
+ use knight::food::{Self, FoodToken};
/// The token does not exist
const ETOKEN_DOES_NOT_EXIST: u64 = 1;
@@ -80,7 +81,7 @@ module token_objects::knight {
#[view]
/// Returns the knight token address by name
public fun knight_token_address(knight_token_name: String): address {
- token::create_token_address(&@token_objects, &string::utf8(KNIGHT_COLLECTION_NAME), &knight_token_name)
+ token::create_token_address(&@knight, &string::utf8(KNIGHT_COLLECTION_NAME), &knight_token_name)
}
/// Mints an knight token. This function mints a new knight token and transfers it to the
@@ -115,8 +116,10 @@ module token_objects::knight {
let property_mutator_ref = property_map::generate_mutator_ref(&constructor_ref);
// Transfers the token to the `soul_bound_to` address
- let linear_transfer_ref = object::generate_linear_transfer_ref(&transfer_ref);
- object::transfer_with_ref(linear_transfer_ref, receiver);
+ if (receiver != signer::address_of(creator)) {
+ let linear_transfer_ref = object::generate_linear_transfer_ref(&transfer_ref);
+ object::transfer_with_ref(linear_transfer_ref, receiver);
+ };
// Initializes the knight health point as 0
move_to(&object_signer, HealthPoint { value: 1 });
@@ -215,13 +218,10 @@ module token_objects::knight {
);
}
- #[test_only]
- use std::signer;
-
- #[test(creator = @token_objects, user1 = @0x456)]
+ #[test(creator = @knight, user1 = @0x456)]
public fun test_knight(creator: &signer, user1: &signer) acquires HealthPoint, KnightToken {
- // This test assumes that the creator's address is equal to @token_objects.
- assert!(signer::address_of(creator) == @token_objects, 0);
+ // This test assumes that the creator's address is equal to @knight.
+ assert!(signer::address_of(creator) == @knight, 0);
// ---------------------------------------------------------------------
// Creator creates the collection, and mints corn and meat tokens in it.
diff --git a/aptos-move/move-examples/token_objects/token_lockup/Move.toml b/aptos-move/move-examples/token_objects/token_lockup/Move.toml
index 3d8f62cb13e63..e862986c126ed 100644
--- a/aptos-move/move-examples/token_objects/token_lockup/Move.toml
+++ b/aptos-move/move-examples/token_objects/token_lockup/Move.toml
@@ -1,9 +1,9 @@
[package]
-name = 'Token Lockup'
+name = 'token_lockup'
version = '1.0.0'
[addresses]
-token_objects = "_"
+token_lockup = "_"
[dependencies]
AptosFramework = { local = "../../../framework/aptos-framework" }
diff --git a/aptos-move/move-examples/token_objects/token_lockup/sources/token_lockup.move b/aptos-move/move-examples/token_objects/token_lockup/sources/token_lockup.move
index 2f664d4579961..ec44c7a8335c3 100644
--- a/aptos-move/move-examples/token_objects/token_lockup/sources/token_lockup.move
+++ b/aptos-move/move-examples/token_objects/token_lockup/sources/token_lockup.move
@@ -1,4 +1,4 @@
-module token_objects::token_lockup {
+module token_lockup::token_lockup {
use std::signer;
use std::option;
use std::error;
diff --git a/aptos-move/move-examples/token_objects/token_lockup/sources/unit_tests.move b/aptos-move/move-examples/token_objects/token_lockup/sources/unit_tests.move
index e99a23320453d..c075198e71f1a 100644
--- a/aptos-move/move-examples/token_objects/token_lockup/sources/unit_tests.move
+++ b/aptos-move/move-examples/token_objects/token_lockup/sources/unit_tests.move
@@ -1,24 +1,17 @@
-module token_objects::unit_tests {
- #[test_only]
- use std::signer;
- #[test_only]
+#[test_only]
+module token_lockup::unit_tests {
use aptos_framework::object;
- #[test_only]
use aptos_framework::account;
- #[test_only]
use aptos_framework::timestamp;
- #[test_only]
- use token_objects::token_lockup;
- #[test_only]
- use std::string::{Self};
- #[test_only]
use aptos_token_objects::token::{Token};
+ use std::signer;
+ use std::string::{Self};
+ use token_lockup::token_lockup;
const TEST_START_TIME: u64 = 1000000000;
// 24 hours in one day * 60 minutes in one hour * 60 seconds in one minute * 7 days
const LOCKUP_PERIOD_SECS: u64 = (24 * 60 * 60) * 7;
- #[test_only]
fun setup_test(
creator: &signer,
owner_1: &signer,
@@ -34,12 +27,11 @@ module token_objects::unit_tests {
token_lockup::initialize_collection(creator);
}
- #[test_only]
fun fast_forward_secs(seconds: u64) {
timestamp::update_global_time_for_test_secs(timestamp::now_seconds() + seconds);
}
- #[test (creator = @0xFA, owner_1 = @0xA, owner_2 = @0xB, aptos_framework = @0x1)]
+ #[test(creator = @0xFA, owner_1 = @0xA, owner_2 = @0xB, aptos_framework = @0x1)]
/// Tests transferring multiple tokens to different owners with slightly different initial lockup times
fun test_happy_path(
creator: &signer,
@@ -47,13 +39,7 @@ module token_objects::unit_tests {
owner_2: &signer,
aptos_framework: &signer,
) {
- setup_test(
- creator,
- owner_1,
- owner_2,
- aptos_framework,
- TEST_START_TIME
- );
+ setup_test(creator, owner_1, owner_2, aptos_framework, TEST_START_TIME);
let owner_1_addr = signer::address_of(owner_1);
let owner_2_addr = signer::address_of(owner_2);
@@ -77,7 +63,6 @@ module token_objects::unit_tests {
assert!(token_lockup::view_last_transfer(token_2_obj) == TEST_START_TIME, 1);
assert!(token_lockup::view_last_transfer(token_3_obj) == TEST_START_TIME + 1, 2);
-
// transfer the first token from owner_1 to owner_2
token_lockup::transfer(owner_1, token_1_obj, owner_2_addr);
// transfer the second token from owner_2 to owner_1
@@ -97,7 +82,7 @@ module token_objects::unit_tests {
assert!(object::is_owner(token_3_obj, owner_2_addr), 8);
}
- #[test (creator = @0xFA, owner_1 = @0xA, owner_2 = @0xB, aptos_framework = @0x1)]
+ #[test(creator = @0xFA, owner_1 = @0xA, owner_2 = @0xB, aptos_framework = @0x1)]
#[expected_failure(abort_code = 0x50003, location = aptos_framework::object)]
fun transfer_raw_fail(
creator: &signer,
@@ -105,13 +90,7 @@ module token_objects::unit_tests {
owner_2: &signer,
aptos_framework: &signer,
) {
- setup_test(
- creator,
- owner_1,
- owner_2,
- aptos_framework,
- TEST_START_TIME
- );
+ setup_test(creator, owner_1, owner_2, aptos_framework, TEST_START_TIME);
let token_1_constructor_ref = token_lockup::mint_to(creator, string::utf8(b"Token #1"), signer::address_of(owner_1));
object::transfer_raw(
@@ -121,21 +100,15 @@ module token_objects::unit_tests {
);
}
- #[test (creator = @0xFA, owner_1 = @0xA, owner_2 = @0xB, aptos_framework = @0x1)]
- #[expected_failure(abort_code = 0x50000, location = token_objects::token_lockup)]
+ #[test(creator = @0xFA, owner_1 = @0xA, owner_2 = @0xB, aptos_framework = @0x1)]
+ #[expected_failure(abort_code = 0x50000, location = token_lockup::token_lockup)]
fun transfer_too_early(
creator: &signer,
owner_1: &signer,
owner_2: &signer,
aptos_framework: &signer,
) {
- setup_test(
- creator,
- owner_1,
- owner_2,
- aptos_framework,
- TEST_START_TIME
- );
+ setup_test(creator, owner_1, owner_2, aptos_framework, TEST_START_TIME);
let token_1_constructor_ref = token_lockup::mint_to(creator, string::utf8(b"Token #1"), signer::address_of(owner_1));
let token_1_obj = object::object_from_constructor_ref(&token_1_constructor_ref);
@@ -145,21 +118,15 @@ module token_objects::unit_tests {
token_lockup::transfer(owner_1, token_1_obj, signer::address_of(owner_2));
}
- #[test (creator = @0xFA, owner_1 = @0xA, owner_2 = @0xB, aptos_framework = @0x1)]
- #[expected_failure(abort_code = 0x50001, location = token_objects::token_lockup)]
+ #[test(creator = @0xFA, owner_1 = @0xA, owner_2 = @0xB, aptos_framework = @0x1)]
+ #[expected_failure(abort_code = 0x50001, location = token_lockup::token_lockup)]
fun transfer_wrong_owner(
creator: &signer,
owner_1: &signer,
owner_2: &signer,
aptos_framework: &signer,
) {
- setup_test(
- creator,
- owner_1,
- owner_2,
- aptos_framework,
- TEST_START_TIME
- );
+ setup_test(creator, owner_1, owner_2, aptos_framework, TEST_START_TIME);
let token_1_constructor_ref = token_lockup::mint_to(creator, string::utf8(b"Token #1"), signer::address_of(owner_1));
let token_1_obj = object::object_from_constructor_ref(&token_1_constructor_ref);
diff --git a/developer-docs-site/docs/guides/account-management/key-rotation.md b/developer-docs-site/docs/guides/account-management/key-rotation.md
new file mode 100644
index 0000000000000..9f7246dbe71ab
--- /dev/null
+++ b/developer-docs-site/docs/guides/account-management/key-rotation.md
@@ -0,0 +1,141 @@
+---
+title: "Rotating an authentication key"
+id: "key-rotation"
+---
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+Aptos Move accounts have a public address, an authentication key, a public key, and a private key. The public address is permanent, always matching the account's initial authentication key.
+
+The Aptos account model facilitates the unique ability to rotate an account's private key. Since an account's address is the *initial* authentication key, the ability to sign for an account can be transferred to another private key without changing its public address.
+
+In this guide, we show examples for how to rotate an account's authentication key using a few of the various Aptos SDKs.
+
+Here are the installation links for the SDKs we will cover in this example:
+
+* [Aptos CLI](../../tools/aptos-cli)
+* [Typescript SDK](../../sdks/ts-sdk/index)
+* [Python SDK](../../sdks/python-sdk)
+
+:::warning
+Some of the following examples use private keys. Do not share your private keys with anyone.
+:::
+
+## How to rotate an account's authentication key
+
+
+
+Run the following to initialize two test profiles. Leave the inputs blank both times you're prompted for a private key.
+
+```shell title="Initialize two test profiles on devnet"
+aptos init --profile test_profile_1 --network devnet --assume-yes
+aptos init --profile test_profile_2 --network devnet --assume-yes
+```
+```shell title="Rotate the authentication key for test_profile_1 to test_profile_2's authentication key"
+aptos account rotate-key --profile test_profile_1 --new-private-key
+```
+:::info Where do I view the private key for a profile?
+Public, private, and authentication keys for Aptos CLI profiles are stored in `~/.aptos/config.yaml` if your config is set to `Global` and `/.aptos/config.yaml` if it's set to `Workspace`.
+
+To see your config settings, run `aptos config show-global-config`.
+:::
+
+```shell title="Confirm yes and create a new profile so that you can continue to sign for the resource account"
+Do you want to submit a transaction for a range of [52000 - 78000] Octas at a gas unit price of 100 Octas? [yes/no] >
+yes
+...
+
+Do you want to create a profile for the new key? [yes/no] >
+yes
+...
+
+Enter the name for the profile
+test_profile_1_rotated
+
+Profile test_profile_1_rotated is saved.
+```
+You can now use the profile like any other account.
+
+In your `config.yaml` file, `test_profile_1_rotated` will retain its original public address but have a new public and private key that matches `test_profile_2`.
+
+The authentication keys aren't shown in the `config.yaml` file, but we can verify the change with the following commands:
+
+```shell title="Verify the authentication keys are now equal with view functions"
+# View the authentication key of `test_profile_1_rotated`
+aptos move view --function-id 0x1::account::get_authentication_key --args address:test_profile_1_rotated
+
+# View the authentication key of `test_profile_2`, it should equal the above.
+aptos move view --function-id 0x1::account::get_authentication_key --args address:test_profile_2
+```
+
+```json title="Example output from the previous two commands"
+{
+ "Result": [
+ "0x458fba533b84717c91897cab05047c1dd7ac2ea73e75c77281781f5b7fec180c"
+ ]
+}
+{
+ "Result": [
+ "0x458fba533b84717c91897cab05047c1dd7ac2ea73e75c77281781f5b7fec180c"
+ ]
+}
+```
+
+
+
+
+This program creates two accounts on devnet, Alice and Bob, funds them, then rotates the Alice's authentication key to that of Bob's.
+
+View the full example for this code [here](https://github.com/aptos-labs/aptos-core/tree/main/ecosystem/typescript/sdk/examples/typescript/rotate_key.ts).
+
+The function to rotate is very simple:
+```typescript title="Typescript SDK rotate authentication key function"
+:!: static/sdks/typescript/examples/typescript-esm/rotate_key.ts rotate_key
+```
+Commands to run the example script:
+```shell title="Navigate to the typescript SDK directory, install dependencies and run rotate_key.ts"
+cd ~/aptos-core/ecosystem/typescript/sdk/examples/typescript-esm
+pnpm install && pnpm rotate_key
+```
+```shell title="rotate_key.ts output"
+Account Address Auth Key Private Key Public Key
+------------------------------------------------------------------------------------------------
+Alice 0x213d...031013 '0x213d...031013' '0x00a4...b2887b' '0x859e...08d2a9'
+Bob 0x1c06...ac3bb3 0x1c06...ac3bb3 0xf2be...9486aa 0xbbc1...abb808
+
+...rotating...
+
+Alice 0x213d...031013 '0x1c06...ac3bb3' '0xf2be...9486aa' '0xbbc1...abb808'
+Bob 0x1c06...ac3bb3 0x1c06...ac3bb3 0xf2be...9486aa 0xbbc1...abb808
+```
+
+
+
+This program creates two accounts on devnet, Alice and Bob, funds them, then rotates the Alice's authentication key to that of Bob's.
+
+View the full example for this code [here](https://github.com/aptos-labs/aptos-core/tree/main/ecosystem/python/sdk/examples/rotate-key.py).
+
+Here's the relevant code that rotates Alice's keys to Bob's:
+```python title="Python SDK rotate authentication key function"
+:!: static/sdks/python/examples/rotate-key.py rotate_key
+```
+Commands to run the example script:
+```shell title="Navigate to the python SDK directory, install dependencies and run rotate_key.ts"
+cd ~/aptos-core/ecosystem/python/sdk
+poetry install && poetry run python -m examples.rotate-key
+```
+```shell title="rotate_key.py output"
+Account Address Auth Key Private Key Public Key
+------------------------------------------------------------------------------------------------
+Alice 0x213d...031013 '0x213d...031013' '0x00a4...b2887b' '0x859e...08d2a9'
+Bob 0x1c06...ac3bb3 0x1c06...ac3bb3 0xf2be...9486aa 0xbbc1...abb808
+
+...rotating...
+
+Alice 0x213d...031013 '0x1c06...ac3bb3' '0xf2be...9486aa' '0xbbc1...abb808'
+Bob 0x1c06...ac3bb3 0x1c06...ac3bb3 0xf2be...9486aa 0xbbc1...abb808
+```
+
+
+
\ No newline at end of file
diff --git a/developer-docs-site/docusaurus.config.js b/developer-docs-site/docusaurus.config.js
index 34a23f6320b1d..54632028a11a6 100644
--- a/developer-docs-site/docusaurus.config.js
+++ b/developer-docs-site/docusaurus.config.js
@@ -158,6 +158,10 @@ const config = {
label: "Create NFTs on Aptos",
to: "/category/nft",
},
+ {
+ label: "Examples",
+ to: "/category/examples",
+ },
{
label: "Build E2E Dapp on Aptos",
to: "tutorials/build-e2e-dapp/e2e-dapp-index",
diff --git a/developer-docs-site/sidebars.js b/developer-docs-site/sidebars.js
index bed821451e53f..adbb1f3a932d5 100644
--- a/developer-docs-site/sidebars.js
+++ b/developer-docs-site/sidebars.js
@@ -334,6 +334,20 @@ const sidebars = {
"guides/nfts/nft-minting-tool",
],
},
+ {
+ type: "category",
+ label: "Examples",
+ collapsible: true,
+ collapsed: true,
+ link: {
+ type: "generated-index",
+ title: "Examples",
+ description: "Examples for all the various concepts and tooling used to build on Aptos.",
+ slug: "/category/examples",
+ keywords: ["examples"],
+ },
+ items: ["guides/account-management/key-rotation"],
+ },
{
type: "category",
label: "Build E2E Dapp with Aptos",
diff --git a/ecosystem/python/sdk/examples/rotate-key.py b/ecosystem/python/sdk/examples/rotate-key.py
new file mode 100644
index 0000000000000..e2bd5b99f3e76
--- /dev/null
+++ b/ecosystem/python/sdk/examples/rotate-key.py
@@ -0,0 +1,134 @@
+import asyncio
+
+from aptos_sdk.account import Account, RotationProofChallenge
+from aptos_sdk.account_address import AccountAddress
+from aptos_sdk.async_client import FaucetClient, RestClient
+from aptos_sdk.authenticator import Authenticator
+from aptos_sdk.bcs import Serializer
+from aptos_sdk.ed25519 import PrivateKey
+from aptos_sdk.transactions import (
+ EntryFunction,
+ TransactionArgument,
+ TransactionPayload,
+)
+
+from .common import FAUCET_URL, NODE_URL
+
+WIDTH = 19
+
+
+def truncate(address: str) -> str:
+ return address[0:6] + "..." + address[-6:]
+
+
+def format_account_info(account: Account) -> str:
+ vals = [
+ str(account.address()),
+ account.auth_key(),
+ account.private_key.hex(),
+ str(account.public_key()),
+ ]
+ return "".join([truncate(v).ljust(WIDTH, " ") for v in vals])
+
+
+async def rotate_auth_key_ed_25519_payload(
+ rest_client: RestClient, from_account: Account, private_key: PrivateKey
+) -> TransactionPayload:
+ to_account = Account.load_key(private_key.hex())
+ rotation_proof_challenge = RotationProofChallenge(
+ sequence_number=await rest_client.account_sequence_number(
+ from_account.address()
+ ),
+ originator=from_account.address(),
+ current_auth_key=AccountAddress.from_str(from_account.auth_key()),
+ new_public_key=to_account.public_key().key.encode(),
+ )
+
+ serializer = Serializer()
+ rotation_proof_challenge.serialize(serializer)
+ rotation_proof_challenge_bcs = serializer.output()
+
+ proof_signed_by_from = from_account.sign(rotation_proof_challenge_bcs).data()
+ proof_signed_by_to = to_account.sign(rotation_proof_challenge_bcs).data()
+
+ from_scheme = Authenticator.ED25519
+ from_public_key_bytes = from_account.public_key().key.encode()
+ to_scheme = Authenticator.ED25519
+ to_public_key_bytes = to_account.public_key().key.encode()
+
+ 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(proof_signed_by_from, Serializer.to_bytes),
+ TransactionArgument(proof_signed_by_to, Serializer.to_bytes),
+ ],
+ )
+
+ return TransactionPayload(entry_function)
+
+
+async def main():
+ # Initialize the clients used to interact with the blockchain
+ rest_client = RestClient(NODE_URL)
+ faucet_client = FaucetClient(FAUCET_URL, rest_client)
+
+ # Generate random accounts Alice and Bob
+ alice = Account.generate()
+ bob = Account.generate()
+
+ # Fund both accounts
+ await faucet_client.fund_account(alice.address(), 10_000_000)
+ await faucet_client.fund_account(bob.address(), 10_000_000)
+
+ # Display formatted account info
+ print(
+ "\n"
+ + "Account".ljust(WIDTH, " ")
+ + "Address".ljust(WIDTH, " ")
+ + "Auth Key".ljust(WIDTH, " ")
+ + "Private Key".ljust(WIDTH, " ")
+ + "Public Key".ljust(WIDTH, " ")
+ )
+ print(
+ "-------------------------------------------------------------------------------------------"
+ )
+ print("Alice".ljust(WIDTH, " ") + format_account_info(alice))
+ print("Bob".ljust(WIDTH, " ") + format_account_info(bob))
+
+ print("\n...rotating...\n")
+
+ # :!:>rotate_key
+ # Create the payload for rotating Alice's private key to Bob's private key
+ payload = await rotate_auth_key_ed_25519_payload(
+ rest_client, alice, bob.private_key
+ )
+ # Have Alice sign the transaction with the payload
+ signed_transaction = await rest_client.create_bcs_signed_transaction(alice, payload)
+ # Submit the transaction and wait for confirmation
+ tx_hash = await rest_client.submit_bcs_transaction(signed_transaction)
+ await rest_client.wait_for_transaction(tx_hash) # <:!:rotate_key
+
+ # Check the authentication key for Alice's address on-chain
+ alice_new_account_info = await rest_client.account(alice.address())
+ # Ensure that Alice's authentication key matches bob's
+ assert (
+ alice_new_account_info["authentication_key"] == bob.auth_key()
+ ), "Authentication key doesn't match Bob's"
+
+ # Construct a new Account object that reflects alice's original address with the new private key
+ alice = Account(alice.address(), bob.private_key)
+
+ # Display formatted account info
+ print("Alice".ljust(WIDTH, " ") + format_account_info(alice))
+ print("Bob".ljust(WIDTH, " ") + format_account_info(bob))
+ print()
+
+
+if __name__ == "__main__":
+ asyncio.run(main())
diff --git a/ecosystem/typescript/sdk/examples/typescript-esm/package.json b/ecosystem/typescript/sdk/examples/typescript-esm/package.json
index d4a7b9bfa1ce5..47287199cd80d 100644
--- a/ecosystem/typescript/sdk/examples/typescript-esm/package.json
+++ b/ecosystem/typescript/sdk/examples/typescript-esm/package.json
@@ -6,7 +6,8 @@
"type": "module",
"scripts": {
"build": "rm -rf dist/* && tsc -p .",
- "test": "pnpm build && node ./dist/index.js"
+ "test": "pnpm build && node ./dist/index.js",
+ "rotate_key": "ts-node --esm rotate_key.ts"
},
"keywords": [],
"author": "",
@@ -15,6 +16,8 @@
"aptos": "latest"
},
"devDependencies": {
+ "@types/node": "18.6.2",
+ "ts-node": "10.9.1",
"typescript": "4.8.2"
}
}
diff --git a/ecosystem/typescript/sdk/examples/typescript-esm/pnpm-lock.yaml b/ecosystem/typescript/sdk/examples/typescript-esm/pnpm-lock.yaml
index f0c6b7599a766..6147ca7322f96 100644
--- a/ecosystem/typescript/sdk/examples/typescript-esm/pnpm-lock.yaml
+++ b/ecosystem/typescript/sdk/examples/typescript-esm/pnpm-lock.yaml
@@ -1,17 +1,50 @@
lockfileVersion: '6.0'
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
dependencies:
aptos:
specifier: latest
- version: 1.7.2
+ version: 1.11.0
devDependencies:
+ '@types/node':
+ specifier: 18.6.2
+ version: 18.6.2
+ ts-node:
+ specifier: 10.9.1
+ version: 10.9.1(@types/node@18.6.2)(typescript@4.8.2)
typescript:
specifier: 4.8.2
version: 4.8.2
packages:
+ /@cspotcode/source-map-support@0.8.1:
+ resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
+ engines: {node: '>=12'}
+ dependencies:
+ '@jridgewell/trace-mapping': 0.3.9
+ dev: true
+
+ /@jridgewell/resolve-uri@3.1.1:
+ resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==}
+ engines: {node: '>=6.0.0'}
+ dev: true
+
+ /@jridgewell/sourcemap-codec@1.4.15:
+ resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==}
+ dev: true
+
+ /@jridgewell/trace-mapping@0.3.9:
+ resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.1
+ '@jridgewell/sourcemap-codec': 1.4.15
+ dev: true
+
/@noble/hashes@1.1.3:
resolution: {integrity: sha512-CE0FCR57H2acVI5UOzIGSSIYxZ6v/HOhDR0Ro9VLyhnzLwx0o8W1mmgaqlEUx4049qJDlIBRztv5k+MM8vbO3A==}
dev: false
@@ -27,8 +60,39 @@ packages:
'@scure/base': 1.1.1
dev: false
- /aptos@1.7.2:
- resolution: {integrity: sha512-unM7bPbu3UGoVB/EhTvA+QDo8nqb6pDfqttsKwC7nYavQnl4t5dxCoFfIFcbijBtSOTfo4is5ldi4Uz4cY9ESA==}
+ /@tsconfig/node10@1.0.9:
+ resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==}
+ dev: true
+
+ /@tsconfig/node12@1.0.11:
+ resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==}
+ dev: true
+
+ /@tsconfig/node14@1.0.3:
+ resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==}
+ dev: true
+
+ /@tsconfig/node16@1.0.4:
+ resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==}
+ dev: true
+
+ /@types/node@18.6.2:
+ resolution: {integrity: sha512-KcfkBq9H4PI6Vpu5B/KoPeuVDAbmi+2mDBqGPGUgoL7yXQtcWGu2vJWmmRkneWK3Rh0nIAX192Aa87AqKHYChQ==}
+ dev: true
+
+ /acorn-walk@8.2.0:
+ resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==}
+ engines: {node: '>=0.4.0'}
+ dev: true
+
+ /acorn@8.10.0:
+ resolution: {integrity: sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==}
+ engines: {node: '>=0.4.0'}
+ hasBin: true
+ dev: true
+
+ /aptos@1.11.0:
+ resolution: {integrity: sha512-hLoyocm3Mv9JQadUObgAJHI4OLJvg2UguOb5Hz7HBbZ05UrpKSCPPX4jTF5UJVlW8HS8b6QQT5dYYXLQHPosqg==}
engines: {node: '>=11.0.0'}
dependencies:
'@noble/hashes': 1.1.3
@@ -40,6 +104,10 @@ packages:
- debug
dev: false
+ /arg@4.1.3:
+ resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
+ dev: true
+
/asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
dev: false
@@ -60,11 +128,20 @@ packages:
delayed-stream: 1.0.0
dev: false
+ /create-require@1.1.1:
+ resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
+ dev: true
+
/delayed-stream@1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
dev: false
+ /diff@4.0.2:
+ resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
+ engines: {node: '>=0.3.1'}
+ dev: true
+
/follow-redirects@1.15.2:
resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==}
engines: {node: '>=4.0'}
@@ -84,6 +161,10 @@ packages:
mime-types: 2.1.35
dev: false
+ /make-error@1.3.6:
+ resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
+ dev: true
+
/mime-db@1.52.0:
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
engines: {node: '>= 0.6'}
@@ -96,6 +177,37 @@ packages:
mime-db: 1.52.0
dev: false
+ /ts-node@10.9.1(@types/node@18.6.2)(typescript@4.8.2):
+ resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==}
+ hasBin: true
+ peerDependencies:
+ '@swc/core': '>=1.2.50'
+ '@swc/wasm': '>=1.2.50'
+ '@types/node': '*'
+ typescript: '>=2.7'
+ peerDependenciesMeta:
+ '@swc/core':
+ optional: true
+ '@swc/wasm':
+ optional: true
+ dependencies:
+ '@cspotcode/source-map-support': 0.8.1
+ '@tsconfig/node10': 1.0.9
+ '@tsconfig/node12': 1.0.11
+ '@tsconfig/node14': 1.0.3
+ '@tsconfig/node16': 1.0.4
+ '@types/node': 18.6.2
+ acorn: 8.10.0
+ acorn-walk: 8.2.0
+ arg: 4.1.3
+ create-require: 1.1.1
+ diff: 4.0.2
+ make-error: 1.3.6
+ typescript: 4.8.2
+ v8-compile-cache-lib: 3.0.1
+ yn: 3.1.1
+ dev: true
+
/tweetnacl@1.0.3:
resolution: {integrity: sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==}
dev: false
@@ -105,3 +217,12 @@ packages:
engines: {node: '>=4.2.0'}
hasBin: true
dev: true
+
+ /v8-compile-cache-lib@3.0.1:
+ resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
+ dev: true
+
+ /yn@3.1.1:
+ resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
+ engines: {node: '>=6'}
+ dev: true
diff --git a/ecosystem/typescript/sdk/examples/typescript-esm/rotate_key.ts b/ecosystem/typescript/sdk/examples/typescript-esm/rotate_key.ts
new file mode 100644
index 0000000000000..e9d34cc87ccae
--- /dev/null
+++ b/ecosystem/typescript/sdk/examples/typescript-esm/rotate_key.ts
@@ -0,0 +1,62 @@
+import { AptosAccount, FaucetClient, Network, Provider, HexString } from "aptos";
+
+const NETWORK = Network.DEVNET;
+const FAUCET_URL = `https://faucet.${NETWORK}.aptoslabs.com`;
+const WIDTH = 16;
+const APTOS_COIN_DECIMALS = 8;
+
+function truncate(address: HexString): string {
+ return `${address.toString().substring(0, 6)}...${address
+ .toString()
+ .substring(address.toString().length - 4, address.toString().length)}`;
+}
+
+function formatAccountInfo(account: AptosAccount): string {
+ const vals: any[] = [
+ account.address(),
+ account.authKey(),
+ HexString.fromUint8Array(account.signingKey.secretKey),
+ HexString.fromUint8Array(account.signingKey.publicKey),
+ ];
+
+ return vals
+ .map((v) => {
+ return truncate(v).padEnd(WIDTH);
+ })
+ .join(" ");
+}
+
+(async () => {
+ const provider = new Provider(NETWORK);
+ const faucetClient = new FaucetClient(provider.aptosClient.nodeUrl, FAUCET_URL);
+
+ // :!:>create_accounts
+ const alice = new AptosAccount();
+ const bob = new AptosAccount(); // <:!:create_accounts
+
+ await faucetClient.fundAccount(alice.address(), 1 * Math.pow(10, APTOS_COIN_DECIMALS));
+ await faucetClient.fundAccount(bob.address(), 1 * Math.pow(10, APTOS_COIN_DECIMALS));
+
+ console.log(
+ `\n${"Account".padEnd(WIDTH)} ${"Address".padEnd(WIDTH)} ${"Auth Key".padEnd(WIDTH)} ${"Private Key".padEnd(
+ WIDTH,
+ )} ${"Public Key".padEnd(WIDTH)}`,
+ );
+ console.log(`---------------------------------------------------------------------------------`);
+ console.log(`${"alice".padEnd(WIDTH)} ${formatAccountInfo(alice)}`);
+ console.log(`${"bob".padEnd(WIDTH)} ${formatAccountInfo(bob)}`);
+ console.log("\n...rotating...".padStart(WIDTH));
+
+ // Rotate the key!
+ // :!:>rotate_key
+ const response = await provider.aptosClient.rotateAuthKeyEd25519(alice, bob.signingKey.secretKey); // <:!:rotate_key
+
+ // We must create a new instance of AptosAccount because the private key has changed.
+ const aliceNew = new AptosAccount(
+ bob.signingKey.secretKey,
+ alice.address(), // NOTE: Without this argument, this would be bob, not aliceNew. You must specify the address since the private key matches multiple accounts now
+ );
+
+ console.log(`\n${"alice".padEnd(WIDTH)} ${formatAccountInfo(aliceNew)}`);
+ console.log(`${"bob".padEnd(WIDTH)} ${formatAccountInfo(bob)}\n`);
+})();