diff --git a/aptos-move/framework/aptos-framework/sources/account/account.move b/aptos-move/framework/aptos-framework/sources/account/account.move index 687179a57f6..47d600bcda8 100644 --- a/aptos-move/framework/aptos-framework/sources/account/account.move +++ b/aptos-move/framework/aptos-framework/sources/account/account.move @@ -17,12 +17,14 @@ module aptos_framework::account { use aptos_std::multi_ed25519; use aptos_std::single_key; use aptos_std::multi_key; + use aptos_std::single_key::AnyPublicKey; use aptos_std::table::{Self, Table}; use aptos_std::type_info::{Self, TypeInfo}; friend aptos_framework::aptos_account; friend aptos_framework::coin; friend aptos_framework::genesis; + friend aptos_framework::keyless_account; friend aptos_framework::multisig_account; friend aptos_framework::resource_account; friend aptos_framework::transaction_validation; @@ -495,9 +497,9 @@ module aptos_framework::account { }); } - /// Upserts an ED25519 backup key to an account that has a keyless public key as its original public key by converting the account's authentication key + /// Upserts an Ed25519 backup key to an account that has a keyless public key as its original public key by converting the account's authentication key /// to a multi-key of the original keyless public key and the new backup key that requires 1 signature from either key to authenticate. - /// This function takes a the account's original keyless public key and a ED25519 backup public key and rotates the account's authentication key to a multi-key of + /// This function takes the account's original keyless public key and a Ed25519 backup public key and rotates the account's authentication key to a multi-key of /// the original keyless public key and the new backup key that requires 1 signature from either key to authenticate. /// /// Note: This function emits a `KeyRotationToMultiPublicKey` event marking both keys as verified since the keyless public key @@ -506,7 +508,7 @@ module aptos_framework::account { /// # Arguments /// * `account` - The signer representing the keyless account /// * `keyless_public_key` - The original keyless public key of the account (wrapped in an AnyPublicKey) - /// * `backup_public_key` - The ED25519 public key to add as a backup + /// * `backup_public_key` - The Ed25519 public key to add as a backup /// * `backup_key_proof` - A signature from the backup key proving ownership /// /// # Aborts @@ -518,8 +520,27 @@ module aptos_framework::account { /// # Events /// * Emits a `KeyRotationToMultiPublicKey` event with the new multi-key configuration entry fun upsert_ed25519_backup_key_on_keyless_account(account: &signer, keyless_public_key: vector, backup_public_key: vector, backup_key_proof: vector) acquires Account { - // Check that the provided public key is a keyless public key let keyless_single_key = single_key::new_public_key_from_bytes(keyless_public_key); + + upsert_ed25519_backup_key_on_keyless_account_internal( + account, + keyless_single_key, + backup_public_key, + backup_key_proof + ) + } + + /// Arguments: + /// keyless_single_key expected to be an `AnyPublicKey::Keyless` or an `AnyPublicKey::FederatedKeyless` + /// + /// Note: The way this was written makes it hard to trivially change it to use the more type-safe `keyless::PublicKey` rather than `AnyPublicKey` + public(friend) fun upsert_ed25519_backup_key_on_keyless_account_internal( + account: &signer, + keyless_single_key: AnyPublicKey, + backup_public_key: vector, + backup_key_proof: vector + ) acquires Account { + // Check that the provided public key is a keyless public key assert!(single_key::is_keyless_or_federated_keyless_public_key(&keyless_single_key), error::invalid_argument(ENOT_A_KEYLESS_PUBLIC_KEY)); let addr = signer::address_of(account); diff --git a/aptos-move/framework/aptos-framework/sources/keyless_account.move b/aptos-move/framework/aptos-framework/sources/keyless_account.move index 53c4b5c9dd9..99e85bb8aa0 100644 --- a/aptos-move/framework/aptos-framework/sources/keyless_account.move +++ b/aptos-move/framework/aptos-framework/sources/keyless_account.move @@ -3,11 +3,14 @@ module aptos_framework::keyless_account { use std::bn254_algebra; use std::config_buffer; + use std::error; use std::option::Option; use std::signer; use std::string::String; use aptos_std::crypto_algebra; use aptos_std::ed25519; + use aptos_std::single_key; + use aptos_framework::account; use aptos_framework::chain_status; use aptos_framework::system_addresses; @@ -23,6 +26,13 @@ module aptos_framework::keyless_account { /// A serialized BN254 G2 point is invalid. const E_INVALID_BN254_G2_SERIALIZATION: u64 = 3; + /// The encrypted payload exceeds `MAX_ENCRYPTED_PAYLOAD_BYTES`. + const E_ENCRYPTED_PAYLOAD_TOO_LONG: u64 = 4; + + /// Maximum size of the encrypted payload stored in `KeylessAccountStorage`. + /// 256 bytes is generous for an encrypted 32-byte DK (key + nonce + auth tag). + const MAX_ENCRYPTED_PAYLOAD_BYTES: u64 = 256; + #[resource_group(scope = global)] struct Group {} @@ -132,6 +142,14 @@ module aptos_framework::keyless_account { max_jwt_header_b64_bytes: u32, } + /// An on-chain struct for keyless accounts to store various data in. Initial use case is for storing an encryption + /// of their confidential asset decryption key. + enum KeylessAccountStorage has key, store, drop { + V1 { + encrypted_payload: vector + } + } + #[test_only] public fun initialize_for_test(fx: &signer, vk: Groth16VerificationKey, constants: Configuration) { system_addresses::assert_aptos_framework(fx); @@ -211,6 +229,48 @@ module aptos_framework::keyless_account { move_to(fx, config); } + /// Atomically performs an `account::upsert_ed25519_backup_key_on_keyless_account` and stores an encrypted payload + /// for the user. Currently used for backing up decryption keys on-chain in Petra for keyless accounts. + entry fun upsert_ed25519_backup_key_on_keyless_account_with_encrypted_payload( + account: &signer, + keyless_public_key: vector, + backup_public_key: vector, + backup_key_proof: vector, + encrypted_payload: vector + ) acquires KeylessAccountStorage { + // Step 1: Perform the key rotation/installation + let keyless_single_key = single_key::new_public_key_from_bytes(keyless_public_key); + + account::upsert_ed25519_backup_key_on_keyless_account_internal( + account, + keyless_single_key, + backup_public_key, + backup_key_proof + ); + + // Step 2: Validate and store the encrypted payload + assert!( + encrypted_payload.length() <= MAX_ENCRYPTED_PAYLOAD_BYTES, + error::invalid_argument(E_ENCRYPTED_PAYLOAD_TOO_LONG) + ); + let addr = signer::address_of(account); + if (!exists(addr)) { + move_to(account, KeylessAccountStorage::V1 { encrypted_payload }); + } else { + KeylessAccountStorage[addr].encrypted_payload = encrypted_payload; + }; + } + + #[view] + /// Returns the encrypted payload stored for `addr`, or an empty vector if none exists. + public fun get_encrypted_payload(addr: address): vector acquires KeylessAccountStorage { + if (exists(addr)) { + KeylessAccountStorage[addr].encrypted_payload + } else { + vector[] + } + } + #[deprecated] public fun update_training_wheels(fx: &signer, pk: Option>) acquires Configuration { system_addresses::assert_aptos_framework(fx); diff --git a/aptos-move/framework/aptos-stdlib/sources/cryptography/keyless.move b/aptos-move/framework/aptos-stdlib/sources/cryptography/keyless.move index ce1d1796cb4..b6692e1cb71 100644 --- a/aptos-move/framework/aptos-stdlib/sources/cryptography/keyless.move +++ b/aptos-move/framework/aptos-stdlib/sources/cryptography/keyless.move @@ -37,7 +37,7 @@ module aptos_std::keyless { // Structs // - /// An *unvalidated* any public key: not necessarily an elliptic curve point, just a sequence of 32 bytes + /// An *unvalidated* keyless public key struct PublicKey has copy, drop, store { iss: String, idc: vector