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`); +})();