diff --git a/aptos-move/framework/aptos-framework/doc/overview.md b/aptos-move/framework/aptos-framework/doc/overview.md
index 314baa3612ba96..7ad32f3ea8ec0d 100644
--- a/aptos-move/framework/aptos-framework/doc/overview.md
+++ b/aptos-move/framework/aptos-framework/doc/overview.md
@@ -46,6 +46,7 @@ This is the reference documentation of the Aptos framework.
- [`0x1::object`](object.md#0x1_object)
- [`0x1::object_code_deployment`](object_code_deployment.md#0x1_object_code_deployment)
- [`0x1::optional_aggregator`](optional_aggregator.md#0x1_optional_aggregator)
+- [`0x1::permissioned_signer`](permissioned_signer.md#0x1_permissioned_signer)
- [`0x1::primary_fungible_store`](primary_fungible_store.md#0x1_primary_fungible_store)
- [`0x1::randomness`](randomness.md#0x1_randomness)
- [`0x1::randomness_api_v0_config`](randomness_api_v0_config.md#0x1_randomness_api_v0_config)
diff --git a/aptos-move/framework/aptos-framework/doc/permissioned_signer.md b/aptos-move/framework/aptos-framework/doc/permissioned_signer.md
new file mode 100644
index 00000000000000..8c5a2763ccc3b0
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/doc/permissioned_signer.md
@@ -0,0 +1,981 @@
+
+
+
+# Module `0x1::permissioned_signer`
+
+A _permissioned signer_ consists of a pair of the original signer and a generated
+signer which is used store information about associated permissions.
+
+A permissioned signer behaves compatible with the original signer as it comes to move_to
, address_of
, and
+existing basic signer functionality. However, the permissions can be queried to assert additional
+restrictions on the use of the signer.
+
+A client which is interested in restricting access granted via a signer can create a permissioned signer
+and pass on to other existing code without changes to existing APIs. Core functions in the framework, for
+example account functions, can then assert availability of permissions, effectively restricting
+existing code in a compatible way.
+
+After introducing the core functionality, examples are provided for withdraw limit on accounts, and
+for blind signing.
+
+
+- [Resource `GrantedPermissionHandles`](#0x1_permissioned_signer_GrantedPermissionHandles)
+- [Struct `PermissionedHandle`](#0x1_permissioned_signer_PermissionedHandle)
+- [Struct `StorablePermissionedHandle`](#0x1_permissioned_signer_StorablePermissionedHandle)
+- [Resource `PermStorage`](#0x1_permissioned_signer_PermStorage)
+- [Struct `Permission`](#0x1_permissioned_signer_Permission)
+- [Constants](#@Constants_0)
+- [Function `create_permissioned_handle`](#0x1_permissioned_signer_create_permissioned_handle)
+- [Function `create_storable_permissioned_handle`](#0x1_permissioned_signer_create_storable_permissioned_handle)
+- [Function `destroy_permissioned_handle`](#0x1_permissioned_signer_destroy_permissioned_handle)
+- [Function `destroy_storable_permissioned_handle`](#0x1_permissioned_signer_destroy_storable_permissioned_handle)
+- [Function `signer_from_permissioned`](#0x1_permissioned_signer_signer_from_permissioned)
+- [Function `signer_from_storable_permissioned`](#0x1_permissioned_signer_signer_from_storable_permissioned)
+- [Function `revoke_permission_handle`](#0x1_permissioned_signer_revoke_permission_handle)
+- [Function `permission_address`](#0x1_permissioned_signer_permission_address)
+- [Function `assert_master_signer`](#0x1_permissioned_signer_assert_master_signer)
+- [Function `authorize`](#0x1_permissioned_signer_authorize)
+- [Function `check_permission`](#0x1_permissioned_signer_check_permission)
+- [Function `capacity`](#0x1_permissioned_signer_capacity)
+- [Function `revoke_permission`](#0x1_permissioned_signer_revoke_permission)
+- [Function `extract_permission`](#0x1_permissioned_signer_extract_permission)
+- [Function `get_key`](#0x1_permissioned_signer_get_key)
+- [Function `address_of`](#0x1_permissioned_signer_address_of)
+- [Function `consume_permission`](#0x1_permissioned_signer_consume_permission)
+- [Function `store_permission`](#0x1_permissioned_signer_store_permission)
+- [Function `is_permissioned_signer`](#0x1_permissioned_signer_is_permissioned_signer)
+- [Function `permission_signer`](#0x1_permissioned_signer_permission_signer)
+- [Function `signer_from_permissioned_impl`](#0x1_permissioned_signer_signer_from_permissioned_impl)
+
+
+
use 0x1::copyable_any;
+use 0x1::error;
+use 0x1::option;
+use 0x1::signer;
+use 0x1::smart_table;
+use 0x1::timestamp;
+use 0x1::transaction_context;
+use 0x1::vector;
+
+
+
+
+
+
+## Resource `GrantedPermissionHandles`
+
+
+
+struct GrantedPermissionHandles has key
+
+
+
+
+
+Fields
+
+
+
+-
+
active_handles: vector<address>
+
+-
+
+
+-
+
revoked_handles: vector<address>
+
+-
+
+
+
+
+
+
+
+
+
+## Struct `PermissionedHandle`
+
+
+
+struct PermissionedHandle
+
+
+
+
+
+Fields
+
+
+
+-
+
master_addr: address
+
+-
+
+
+-
+
permission_addr: address
+
+-
+
+
+
+
+
+
+
+
+
+## Struct `StorablePermissionedHandle`
+
+
+
+struct StorablePermissionedHandle has store
+
+
+
+
+
+Fields
+
+
+
+-
+
master_addr: address
+
+-
+
+
+-
+
permission_addr: address
+
+-
+
+
+-
+
expiration_time: u64
+
+-
+
+
+
+
+
+
+
+
+
+## Resource `PermStorage`
+
+
+
+struct PermStorage has key
+
+
+
+
+
+Fields
+
+
+
+-
+
perms: smart_table::SmartTable<copyable_any::Any, u256>
+
+-
+
+
+
+
+
+
+
+
+
+## Struct `Permission`
+
+
+
+struct Permission<K>
+
+
+
+
+
+Fields
+
+
+
+-
+
owner_address: address
+
+-
+
+
+-
+
key: K
+
+-
+
+
+-
+
capacity: u256
+
+-
+
+
+
+
+
+
+
+
+
+## Constants
+
+
+
+
+Cannot authorize a permission.
+
+
+const ECANNOT_AUTHORIZE: u64 = 2;
+
+
+
+
+
+
+signer doesn't have enough capacity to extract permission.
+
+
+const ECANNOT_EXTRACT_PERMISSION: u64 = 4;
+
+
+
+
+
+
+Trying to grant permission using master signer.
+
+
+const ENOT_MASTER_SIGNER: u64 = 1;
+
+
+
+
+
+
+Access permission information from a master signer.
+
+
+const ENOT_PERMISSIONED_SIGNER: u64 = 3;
+
+
+
+
+
+
+permission handle has expired.
+
+
+const E_PERMISSION_EXPIRED: u64 = 5;
+
+
+
+
+
+
+storing extracted permission into a different signer.
+
+
+const E_PERMISSION_MISMATCH: u64 = 6;
+
+
+
+
+
+
+permission handle has been revoked by the original signer.
+
+
+const E_PERMISSION_REVOKED: u64 = 7;
+
+
+
+
+
+
+## Function `create_permissioned_handle`
+
+
+
+public fun create_permissioned_handle(master: &signer): permissioned_signer::PermissionedHandle
+
+
+
+
+
+Implementation
+
+
+public fun create_permissioned_handle(master: &signer): PermissionedHandle {
+ let permission_addr = generate_auid_address();
+ let master_addr = signer::address_of(master);
+
+ PermissionedHandle {
+ master_addr,
+ permission_addr,
+ }
+}
+
+
+
+
+
+
+
+
+## Function `create_storable_permissioned_handle`
+
+
+
+public(friend) fun create_storable_permissioned_handle(master: &signer, expiration_time: u64): permissioned_signer::StorablePermissionedHandle
+
+
+
+
+
+Implementation
+
+
+public(friend) fun create_storable_permissioned_handle(master: &signer, expiration_time: u64): StorablePermissionedHandle acquires GrantedPermissionHandles {
+ let permission_addr = generate_auid_address();
+ let master_addr = signer::address_of(master);
+
+ if(!exists<GrantedPermissionHandles>(master_addr)) {
+ move_to<GrantedPermissionHandles>(master, GrantedPermissionHandles {
+ active_handles: vector::empty(),
+ revoked_handles: vector::empty(),
+ });
+ };
+
+ vector::push_back(
+ &mut borrow_global_mut<GrantedPermissionHandles>(master_addr).active_handles,
+ permission_addr
+ );
+
+ // Do we need to move sth similar to ObjectCore to register this address as permission address?
+ StorablePermissionedHandle {
+ master_addr,
+ permission_addr,
+ expiration_time,
+ }
+}
+
+
+
+
+
+
+
+
+## Function `destroy_permissioned_handle`
+
+
+
+public fun destroy_permissioned_handle(p: permissioned_signer::PermissionedHandle)
+
+
+
+
+
+Implementation
+
+
+public fun destroy_permissioned_handle(p: PermissionedHandle) acquires PermStorage {
+ let PermissionedHandle { master_addr: _, permission_addr } = p;
+ if(exists<PermStorage>(permission_addr)) {
+ let PermStorage { perms } = move_from<PermStorage>(permission_addr);
+ smart_table::destroy(perms);
+ };
+}
+
+
+
+
+
+
+
+
+## Function `destroy_storable_permissioned_handle`
+
+
+
+public fun destroy_storable_permissioned_handle(p: permissioned_signer::StorablePermissionedHandle)
+
+
+
+
+
+Implementation
+
+
+public fun destroy_storable_permissioned_handle(p: StorablePermissionedHandle) acquires PermStorage, GrantedPermissionHandles {
+ let StorablePermissionedHandle { master_addr, permission_addr, expiration_time: _ } = p;
+ if(exists<PermStorage>(permission_addr)) {
+ let PermStorage { perms } = move_from<PermStorage>(permission_addr);
+ smart_table::destroy(perms);
+ };
+ let granted_permissions = borrow_global_mut<GrantedPermissionHandles>(master_addr);
+ let (found, idx) = vector::index_of(&granted_permissions.active_handles, &permission_addr);
+ if(found) {
+ vector::swap_remove(&mut granted_permissions.active_handles, idx);
+ };
+ let (found, idx) = vector::index_of(&granted_permissions.revoked_handles, &permission_addr);
+ if(found) {
+ vector::swap_remove(&mut granted_permissions.revoked_handles, idx);
+ };
+}
+
+
+
+
+
+
+
+
+## Function `signer_from_permissioned`
+
+
+
+public fun signer_from_permissioned(p: &permissioned_signer::PermissionedHandle): signer
+
+
+
+
+
+Implementation
+
+
+public fun signer_from_permissioned(p: &PermissionedHandle): signer {
+ signer_from_permissioned_impl(p.master_addr, p.permission_addr)
+}
+
+
+
+
+
+
+
+
+## Function `signer_from_storable_permissioned`
+
+
+
+public fun signer_from_storable_permissioned(p: &permissioned_signer::StorablePermissionedHandle): signer
+
+
+
+
+
+Implementation
+
+
+public fun signer_from_storable_permissioned(p: &StorablePermissionedHandle): signer acquires GrantedPermissionHandles {
+ assert!(timestamp::now_seconds() < p.expiration_time, error::permission_denied(E_PERMISSION_EXPIRED));
+ assert!(
+ !vector::contains(
+ &borrow_global<GrantedPermissionHandles>(p.master_addr).revoked_handles,
+ &p.permission_addr
+ ),
+ error::permission_denied(E_PERMISSION_REVOKED)
+ );
+ signer_from_permissioned_impl(p.master_addr, p.permission_addr)
+}
+
+
+
+
+
+
+
+
+## Function `revoke_permission_handle`
+
+
+
+public fun revoke_permission_handle(s: &signer, permission_addr: address)
+
+
+
+
+
+Implementation
+
+
+public fun revoke_permission_handle(s: &signer, permission_addr: address) acquires GrantedPermissionHandles {
+ assert!(!is_permissioned_signer(s), error::permission_denied(ENOT_MASTER_SIGNER));
+ let granted_permissions = borrow_global_mut<GrantedPermissionHandles>(signer::address_of(s));
+ if(!vector::contains(&granted_permissions.revoked_handles, &permission_addr)) {
+ vector::push_back(&mut granted_permissions.revoked_handles, permission_addr)
+ }
+}
+
+
+
+
+
+
+
+
+## Function `permission_address`
+
+
+
+public fun permission_address(p: &permissioned_signer::PermissionedHandle): address
+
+
+
+
+
+Implementation
+
+
+public fun permission_address(p: &PermissionedHandle): address {
+ p.permission_addr
+}
+
+
+
+
+
+
+
+
+## Function `assert_master_signer`
+
+
+
+public fun assert_master_signer(s: &signer)
+
+
+
+
+
+Implementation
+
+
+public fun assert_master_signer(s: &signer) {
+ assert!(!is_permissioned_signer(s), error::permission_denied(ENOT_MASTER_SIGNER));
+}
+
+
+
+
+
+
+
+
+## Function `authorize`
+
+=====================================================================================================
+Permission Management
+
+Authorizes permissioned
with the given permission. This requires to have access to the master
+signer.
+
+
+public fun authorize<PermKey: copy, drop, store>(master: &signer, permissioned: &signer, capacity: u256, perm: PermKey)
+
+
+
+
+
+Implementation
+
+
+public fun authorize<PermKey: copy + drop + store>(
+ master: &signer,
+ permissioned: &signer,
+ capacity: u256,
+ perm: PermKey
+) acquires PermStorage {
+ assert!(
+ is_permissioned_signer(permissioned) &&
+ !is_permissioned_signer(master) &&
+ signer::address_of(master) == signer::address_of(permissioned),
+ error::permission_denied(ECANNOT_AUTHORIZE)
+ );
+ let permission_signer = permission_signer(permissioned);
+ let permission_signer_addr = signer::address_of(&permission_signer);
+ if(!exists<PermStorage>(permission_signer_addr)) {
+ move_to(&permission_signer, PermStorage { perms: smart_table::new()});
+ };
+ let perms = &mut borrow_global_mut<PermStorage>(permission_signer_addr).perms;
+ let key = copyable_any::pack(perm);
+ if(smart_table::contains(perms, key)) {
+ let entry = smart_table::borrow_mut(perms, key);
+ *entry = *entry + capacity;
+ } else {
+ smart_table::add(perms, key, capacity);
+ }
+}
+
+
+
+
+
+
+
+
+## Function `check_permission`
+
+Asserts that the given signer has permission PermKey
, and the capacity
+to handle weight
, which will be subtracted from capacity.
+
+
+public fun check_permission<PermKey: copy, drop, store>(s: &signer, weight: u256, perm: PermKey): bool
+
+
+
+
+
+Implementation
+
+
+public fun check_permission<PermKey: copy + drop + store>(
+ s: &signer,
+ weight: u256,
+ perm: PermKey
+): bool acquires PermStorage {
+ if (!is_permissioned_signer(s)) {
+ // master signer has all permissions
+ return true
+ };
+ let addr = signer::address_of(&permission_signer(s));
+ if(!exists<PermStorage>(addr)) {
+ return false
+ };
+ let perm = smart_table::borrow_mut(&mut borrow_global_mut<PermStorage>(addr).perms, copyable_any::pack(perm));
+ if(*perm < weight) {
+ return false
+ };
+ *perm = *perm - weight;
+ return true
+}
+
+
+
+
+
+
+
+
+## Function `capacity`
+
+
+
+public fun capacity<PermKey: copy, drop, store>(s: &signer, perm: PermKey): option::Option<u256>
+
+
+
+
+
+Implementation
+
+
+public fun capacity<PermKey: copy + drop + store>(s: &signer, perm: PermKey): Option<u256> acquires PermStorage {
+ assert!(is_permissioned_signer(s), error::permission_denied(ENOT_PERMISSIONED_SIGNER));
+ let addr = signer::address_of(&permission_signer(s));
+ if(!exists<PermStorage>(addr)) {
+ return option::none()
+ };
+ let perm_storage = &borrow_global<PermStorage>(addr).perms;
+ let key = copyable_any::pack(perm);
+ if(smart_table::contains(perm_storage, key)) {
+ option::some(*smart_table::borrow(&borrow_global<PermStorage>(addr).perms, key))
+ } else {
+ option::none()
+ }
+}
+
+
+
+
+
+
+
+
+## Function `revoke_permission`
+
+
+
+public fun revoke_permission<PermKey: copy, drop, store>(permissioned: &signer, perm: PermKey)
+
+
+
+
+
+Implementation
+
+
+public fun revoke_permission<PermKey: copy + drop + store>(permissioned: &signer, perm: PermKey) acquires PermStorage {
+ if(!is_permissioned_signer(permissioned)) {
+ // Master signer has no permissions associated with it.
+ return
+ };
+ let addr = signer::address_of(&permission_signer(permissioned));
+ if(!exists<PermStorage>(addr)) {
+ return
+ };
+ smart_table::remove(&mut borrow_global_mut<PermStorage>(addr).perms, copyable_any::pack(perm));
+}
+
+
+
+
+
+
+
+
+## Function `extract_permission`
+
+Another flavor of api to extract and store permissions
+
+
+public fun extract_permission<PermKey: copy, drop, store>(s: &signer, weight: u256, perm: PermKey): permissioned_signer::Permission<PermKey>
+
+
+
+
+
+Implementation
+
+
+public fun extract_permission<PermKey: copy + drop + store>(
+ s: &signer,
+ weight: u256,
+ perm: PermKey
+): Permission<PermKey> acquires PermStorage {
+ assert!(check_permission(s, weight, perm), error::permission_denied(ECANNOT_EXTRACT_PERMISSION));
+ Permission {
+ owner_address: signer::address_of(s),
+ key: perm,
+ capacity: weight,
+ }
+}
+
+
+
+
+
+
+
+
+## Function `get_key`
+
+
+
+public fun get_key<PermKey>(perm: &permissioned_signer::Permission<PermKey>): &PermKey
+
+
+
+
+
+Implementation
+
+
+public fun get_key<PermKey>(perm: &Permission<PermKey>): &PermKey {
+ &perm.key
+}
+
+
+
+
+
+
+
+
+## Function `address_of`
+
+
+
+public fun address_of<PermKey>(perm: &permissioned_signer::Permission<PermKey>): address
+
+
+
+
+
+Implementation
+
+
+public fun address_of<PermKey>(perm: &Permission<PermKey>): address {
+ perm.owner_address
+}
+
+
+
+
+
+
+
+
+## Function `consume_permission`
+
+
+
+public fun consume_permission<PermKey: copy, drop, store>(perm: &mut permissioned_signer::Permission<PermKey>, weight: u256, perm_key: PermKey): bool
+
+
+
+
+
+Implementation
+
+
+public fun consume_permission<PermKey: copy + drop + store>(
+ perm: &mut Permission<PermKey>,
+ weight: u256,
+ perm_key: PermKey
+): bool {
+ if(perm.key != perm_key) {
+ return false
+ };
+ if(perm.capacity >= weight) {
+ perm.capacity = perm.capacity - weight;
+ return true
+ } else {
+ return false
+ }
+}
+
+
+
+
+
+
+
+
+## Function `store_permission`
+
+
+
+public fun store_permission<PermKey: copy, drop, store>(s: &signer, perm: permissioned_signer::Permission<PermKey>)
+
+
+
+
+
+Implementation
+
+
+public fun store_permission<PermKey: copy + drop + store>(
+ s: &signer,
+ perm: Permission<PermKey>
+) acquires PermStorage {
+ assert!(is_permissioned_signer(s), error::permission_denied(ENOT_PERMISSIONED_SIGNER));
+ let Permission { key, capacity, owner_address } = perm;
+
+ assert!(signer::address_of(s) == owner_address, error::permission_denied(E_PERMISSION_MISMATCH));
+
+ let permission_signer = permission_signer(s);
+ let permission_signer_addr = signer::address_of(&permission_signer);
+ if(!exists<PermStorage>(permission_signer_addr)) {
+ move_to(&permission_signer, PermStorage { perms: smart_table::new()});
+ };
+ let perms = &mut borrow_global_mut<PermStorage>(permission_signer_addr).perms;
+ let key = copyable_any::pack(key);
+ if(smart_table::contains(perms, key)) {
+ let entry = smart_table::borrow_mut(perms, key);
+ *entry = *entry + capacity;
+ } else {
+ smart_table::add(perms, key, capacity)
+ }
+}
+
+
+
+
+
+
+
+
+## Function `is_permissioned_signer`
+
+Creates a permissioned signer from an existing universal signer. The function aborts if the
+given signer is already a permissioned signer.
+
+The implementation of this function requires to extend the value representation for signers in the VM.
+
+Check whether this is a permissioned signer.
+
+
+public fun is_permissioned_signer(s: &signer): bool
+
+
+
+
+
+Implementation
+
+
+public native fun is_permissioned_signer(s: &signer): bool;
+
+
+
+
+
+
+
+
+## Function `permission_signer`
+
+Return the signer used for storing permissions. Aborts if not a permissioned signer.
+
+
+fun permission_signer(permissioned: &signer): signer
+
+
+
+
+
+Implementation
+
+
+native fun permission_signer(permissioned: &signer): signer;
+
+
+
+
+
+
+
+
+## Function `signer_from_permissioned_impl`
+
+
+invariants:
+signer::address_of(master) == signer::address_of(signer_from_permissioned(create_permissioned_handle(master))),
+
+
+fun signer_from_permissioned_impl(master_addr: address, permission_addr: address): signer
+
+
+
+
+
+Implementation
+
+
+native fun signer_from_permissioned_impl(master_addr: address, permission_addr: address): signer;
+
+
+
+
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/sources/permissioned_signer.move b/aptos-move/framework/aptos-framework/sources/permissioned_signer.move
new file mode 100644
index 00000000000000..8544cf0b9750da
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/sources/permissioned_signer.move
@@ -0,0 +1,334 @@
+/// A _permissioned signer_ consists of a pair of the original signer and a generated
+/// signer which is used store information about associated permissions.
+///
+/// A permissioned signer behaves compatible with the original signer as it comes to `move_to`, `address_of`, and
+/// existing basic signer functionality. However, the permissions can be queried to assert additional
+/// restrictions on the use of the signer.
+///
+/// A client which is interested in restricting access granted via a signer can create a permissioned signer
+/// and pass on to other existing code without changes to existing APIs. Core functions in the framework, for
+/// example account functions, can then assert availability of permissions, effectively restricting
+/// existing code in a compatible way.
+///
+/// After introducing the core functionality, examples are provided for withdraw limit on accounts, and
+/// for blind signing.
+module aptos_framework::permissioned_signer {
+ use std::signer;
+ use std::error;
+ use std::vector;
+ use std::option::{Option, Self};
+ use aptos_std::copyable_any::{Self, Any};
+ use aptos_std::smart_table::{Self, SmartTable};
+ use aptos_framework::transaction_context::generate_auid_address;
+ use aptos_framework::timestamp;
+
+ #[test_only]
+ friend aptos_framework::permissioned_signer_tests;
+
+ /// Trying to grant permission using master signer.
+ const ENOT_MASTER_SIGNER: u64 = 1;
+
+ /// Cannot authorize a permission.
+ const ECANNOT_AUTHORIZE: u64 = 2;
+
+ /// Access permission information from a master signer.
+ const ENOT_PERMISSIONED_SIGNER: u64 = 3;
+
+ /// signer doesn't have enough capacity to extract permission.
+ const ECANNOT_EXTRACT_PERMISSION: u64 = 4;
+
+ /// permission handle has expired.
+ const E_PERMISSION_EXPIRED: u64 = 5;
+
+ /// storing extracted permission into a different signer.
+ const E_PERMISSION_MISMATCH: u64 = 6;
+
+ /// permission handle has been revoked by the original signer.
+ const E_PERMISSION_REVOKED: u64 = 7;
+
+ struct GrantedPermissionHandles has key {
+ active_handles: vector,
+ revoked_handles: vector,
+ }
+
+ struct PermissionedHandle {
+ master_addr: address,
+ permission_addr: address,
+ }
+
+ struct StorablePermissionedHandle has store {
+ master_addr: address,
+ permission_addr: address,
+ expiration_time: u64,
+ }
+
+ struct PermStorage has key {
+ perms: SmartTable,
+ }
+
+ struct Permission {
+ owner_address: address,
+ key: K,
+ capacity: u256,
+ }
+
+ public fun create_permissioned_handle(master: &signer): PermissionedHandle {
+ assert_master_signer(master);
+ let permission_addr = generate_auid_address();
+ let master_addr = signer::address_of(master);
+
+ PermissionedHandle {
+ master_addr,
+ permission_addr,
+ }
+ }
+
+ public(friend) fun create_storable_permissioned_handle(master: &signer, expiration_time: u64): StorablePermissionedHandle acquires GrantedPermissionHandles {
+ assert_master_signer(master);
+ let permission_addr = generate_auid_address();
+ let master_addr = signer::address_of(master);
+
+ if(!exists(master_addr)) {
+ move_to(master, GrantedPermissionHandles {
+ active_handles: vector::empty(),
+ revoked_handles: vector::empty(),
+ });
+ };
+
+ vector::push_back(
+ &mut borrow_global_mut(master_addr).active_handles,
+ permission_addr
+ );
+
+ // Do we need to move sth similar to ObjectCore to register this address as permission address?
+ StorablePermissionedHandle {
+ master_addr,
+ permission_addr,
+ expiration_time,
+ }
+ }
+
+ public fun destroy_permissioned_handle(p: PermissionedHandle) acquires PermStorage {
+ let PermissionedHandle { master_addr: _, permission_addr } = p;
+ if(exists(permission_addr)) {
+ let PermStorage { perms } = move_from(permission_addr);
+ smart_table::destroy(perms);
+ };
+ }
+
+ public fun destroy_storable_permissioned_handle(p: StorablePermissionedHandle) acquires PermStorage, GrantedPermissionHandles {
+ let StorablePermissionedHandle { master_addr, permission_addr, expiration_time: _ } = p;
+ if(exists(permission_addr)) {
+ let PermStorage { perms } = move_from(permission_addr);
+ smart_table::destroy(perms);
+ };
+ let granted_permissions = borrow_global_mut(master_addr);
+ let (found, idx) = vector::index_of(&granted_permissions.active_handles, &permission_addr);
+ if(found) {
+ vector::swap_remove(&mut granted_permissions.active_handles, idx);
+ };
+ let (found, idx) = vector::index_of(&granted_permissions.revoked_handles, &permission_addr);
+ if(found) {
+ vector::swap_remove(&mut granted_permissions.revoked_handles, idx);
+ };
+ }
+
+
+ public fun signer_from_permissioned(p: &PermissionedHandle): signer {
+ signer_from_permissioned_impl(p.master_addr, p.permission_addr)
+ }
+
+ public fun signer_from_storable_permissioned(p: &StorablePermissionedHandle): signer acquires GrantedPermissionHandles {
+ assert!(timestamp::now_seconds() < p.expiration_time, error::permission_denied(E_PERMISSION_EXPIRED));
+ assert!(
+ !vector::contains(
+ &borrow_global(p.master_addr).revoked_handles,
+ &p.permission_addr
+ ),
+ error::permission_denied(E_PERMISSION_REVOKED)
+ );
+ signer_from_permissioned_impl(p.master_addr, p.permission_addr)
+ }
+
+ public fun revoke_permission_handle(s: &signer, permission_addr: address) acquires GrantedPermissionHandles {
+ assert!(!is_permissioned_signer(s), error::permission_denied(ENOT_MASTER_SIGNER));
+ let master_addr = signer::address_of(s);
+ if(!exists(master_addr)) {
+ return
+ };
+ let granted_permissions = borrow_global_mut(master_addr);
+ if(!vector::contains(&granted_permissions.revoked_handles, &permission_addr)) {
+ vector::push_back(&mut granted_permissions.revoked_handles, permission_addr)
+ }
+ }
+
+ public fun permission_address(p: &StorablePermissionedHandle): address {
+ p.permission_addr
+ }
+
+ public fun assert_master_signer(s: &signer) {
+ assert!(!is_permissioned_signer(s), error::permission_denied(ENOT_MASTER_SIGNER));
+ }
+
+ /// =====================================================================================================
+ /// Permission Management
+ ///
+
+ /// Authorizes `permissioned` with the given permission. This requires to have access to the `master`
+ /// signer.
+ public fun authorize(
+ master: &signer,
+ permissioned: &signer,
+ capacity: u256,
+ perm: PermKey
+ ) acquires PermStorage {
+ assert!(
+ is_permissioned_signer(permissioned) &&
+ !is_permissioned_signer(master) &&
+ signer::address_of(master) == signer::address_of(permissioned),
+ error::permission_denied(ECANNOT_AUTHORIZE)
+ );
+ let permission_signer = permission_signer(permissioned);
+ let permission_signer_addr = signer::address_of(&permission_signer);
+ if(!exists(permission_signer_addr)) {
+ move_to(&permission_signer, PermStorage { perms: smart_table::new()});
+ };
+ let perms = &mut borrow_global_mut(permission_signer_addr).perms;
+ let key = copyable_any::pack(perm);
+ if(smart_table::contains(perms, key)) {
+ let entry = smart_table::borrow_mut(perms, key);
+ *entry = *entry + capacity;
+ } else {
+ smart_table::add(perms, key, capacity);
+ }
+ }
+ /// Asserts that the given signer has permission `PermKey`, and the capacity
+ /// to handle `weight`, which will be subtracted from capacity.
+ public fun check_permission(
+ s: &signer,
+ weight: u256,
+ perm: PermKey
+ ): bool acquires PermStorage {
+ if (!is_permissioned_signer(s)) {
+ // master signer has all permissions
+ return true
+ };
+ let addr = signer::address_of(&permission_signer(s));
+ if(!exists(addr)) {
+ return false
+ };
+ let perm = smart_table::borrow_mut(&mut borrow_global_mut(addr).perms, copyable_any::pack(perm));
+ if(*perm < weight) {
+ return false
+ };
+ *perm = *perm - weight;
+ return true
+ }
+
+ public fun capacity(s: &signer, perm: PermKey): Option acquires PermStorage {
+ assert!(is_permissioned_signer(s), error::permission_denied(ENOT_PERMISSIONED_SIGNER));
+ let addr = signer::address_of(&permission_signer(s));
+ if(!exists(addr)) {
+ return option::none()
+ };
+ let perm_storage = &borrow_global(addr).perms;
+ let key = copyable_any::pack(perm);
+ if(smart_table::contains(perm_storage, key)) {
+ option::some(*smart_table::borrow(&borrow_global(addr).perms, key))
+ } else {
+ option::none()
+ }
+ }
+
+ public fun revoke_permission(permissioned: &signer, perm: PermKey) acquires PermStorage {
+ if(!is_permissioned_signer(permissioned)) {
+ // Master signer has no permissions associated with it.
+ return
+ };
+ let addr = signer::address_of(&permission_signer(permissioned));
+ if(!exists(addr)) {
+ return
+ };
+ smart_table::remove(&mut borrow_global_mut(addr).perms, copyable_any::pack(perm));
+ }
+
+ /// Another flavor of api to extract and store permissions
+ public fun extract_permission(
+ s: &signer,
+ weight: u256,
+ perm: PermKey
+ ): Permission acquires PermStorage {
+ assert!(check_permission(s, weight, perm), error::permission_denied(ECANNOT_EXTRACT_PERMISSION));
+ Permission {
+ owner_address: signer::address_of(s),
+ key: perm,
+ capacity: weight,
+ }
+ }
+
+ public fun get_key(perm: &Permission): &PermKey {
+ &perm.key
+ }
+
+ public fun address_of(perm: &Permission): address {
+ perm.owner_address
+ }
+
+ public fun consume_permission(
+ perm: &mut Permission,
+ weight: u256,
+ perm_key: PermKey
+ ): bool {
+ if(perm.key != perm_key) {
+ return false
+ };
+ if(perm.capacity >= weight) {
+ perm.capacity = perm.capacity - weight;
+ return true
+ } else {
+ return false
+ }
+ }
+
+ public fun store_permission(
+ s: &signer,
+ perm: Permission
+ ) acquires PermStorage {
+ assert!(is_permissioned_signer(s), error::permission_denied(ENOT_PERMISSIONED_SIGNER));
+ let Permission { key, capacity, owner_address } = perm;
+
+ assert!(signer::address_of(s) == owner_address, error::permission_denied(E_PERMISSION_MISMATCH));
+
+ let permission_signer = permission_signer(s);
+ let permission_signer_addr = signer::address_of(&permission_signer);
+ if(!exists(permission_signer_addr)) {
+ move_to(&permission_signer, PermStorage { perms: smart_table::new()});
+ };
+ let perms = &mut borrow_global_mut(permission_signer_addr).perms;
+ let key = copyable_any::pack(key);
+ if(smart_table::contains(perms, key)) {
+ let entry = smart_table::borrow_mut(perms, key);
+ *entry = *entry + capacity;
+ } else {
+ smart_table::add(perms, key, capacity)
+ }
+ }
+
+ // =====================================================================================================
+ // Native Functions
+
+ /// Creates a permissioned signer from an existing universal signer. The function aborts if the
+ /// given signer is already a permissioned signer.
+ ///
+ /// The implementation of this function requires to extend the value representation for signers in the VM.
+ ///
+ /// Check whether this is a permissioned signer.
+ public native fun is_permissioned_signer(s: &signer): bool;
+ /// Return the signer used for storing permissions. Aborts if not a permissioned signer.
+ native fun permission_signer(permissioned: &signer): signer;
+ ///
+ /// invariants:
+ /// signer::address_of(master) == signer::address_of(signer_from_permissioned(create_permissioned_handle(master))),
+ ///
+ native fun signer_from_permissioned_impl(master_addr: address, permission_addr: address): signer;
+}
diff --git a/aptos-move/framework/aptos-framework/tests/permissioned_signer_tests.move b/aptos-move/framework/aptos-framework/tests/permissioned_signer_tests.move
new file mode 100644
index 00000000000000..4fbd3c9c3b23ad
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/permissioned_signer_tests.move
@@ -0,0 +1,167 @@
+#[test_only]
+module aptos_framework::permissioned_signer_tests {
+ use aptos_framework::account::create_signer_for_test;
+ use aptos_framework::permissioned_signer;
+ use aptos_framework::timestamp;
+ use std::option;
+ use std::signer;
+
+ struct OnePermission has copy, drop, store {}
+
+ struct AddressPermission has copy, drop, store {
+ addr: address
+ }
+
+
+ #[test(creator = @0xcafe)]
+ fun test_permission_e2e(
+ creator: &signer,
+ ) {
+ let aptos_framework = create_signer_for_test(@0x1);
+ timestamp::set_time_has_started_for_testing(&aptos_framework);
+
+ let perm_handle = permissioned_signer::create_permissioned_handle(creator);
+ let perm_signer = permissioned_signer::signer_from_permissioned(&perm_handle);
+
+ assert!(permissioned_signer::is_permissioned_signer(&perm_signer), 1);
+ assert!(!permissioned_signer::is_permissioned_signer(creator), 1);
+ assert!(signer::address_of(&perm_signer) == signer::address_of(creator), 1);
+
+ permissioned_signer::authorize(creator, &perm_signer, 100, OnePermission {});
+ assert!(permissioned_signer::capacity(&perm_signer, OnePermission {}) == option::some(100), 1);
+
+ assert!(permissioned_signer::check_permission(&perm_signer, 10, OnePermission {}), 1);
+ assert!(permissioned_signer::capacity(&perm_signer, OnePermission {}) == option::some(90), 1);
+
+ permissioned_signer::authorize(creator, &perm_signer, 5, AddressPermission { addr: @0x1 });
+
+ assert!(permissioned_signer::capacity(&perm_signer, AddressPermission { addr: @0x1 }) == option::some(5), 1);
+ assert!(permissioned_signer::capacity(&perm_signer, AddressPermission { addr: @0x2 }) == option::none(), 1);
+
+ // Not enough capacity, check permission should return false
+ assert!(!permissioned_signer::check_permission(&perm_signer, 10, AddressPermission { addr: @0x1 }), 1);
+
+ permissioned_signer::revoke_permission(&perm_signer, OnePermission {});
+ assert!(permissioned_signer::capacity(&perm_signer, OnePermission {}) == option::none(), 1);
+
+ permissioned_signer::destroy_permissioned_handle(perm_handle);
+ }
+
+ #[test(creator = @0xcafe)]
+ #[expected_failure(abort_code = 0x50005, location = aptos_framework::permissioned_signer)]
+ fun test_permission_expiration(
+ creator: &signer,
+ ) {
+ let aptos_framework = create_signer_for_test(@0x1);
+ timestamp::set_time_has_started_for_testing(&aptos_framework);
+
+ let perm_handle = permissioned_signer::create_storable_permissioned_handle(creator, 60);
+ let _perm_signer = permissioned_signer::signer_from_storable_permissioned(&perm_handle);
+
+ timestamp::fast_forward_seconds(60);
+ let _perm_signer = permissioned_signer::signer_from_storable_permissioned(&perm_handle);
+
+ permissioned_signer::destroy_storable_permissioned_handle(perm_handle);
+ }
+
+ // invalid authorization
+ // 1. master signer is a permissioned signer
+ // 2. permissioned signer is a master signer
+ // 3. permissioned and main signer address mismatch
+ #[test(creator = @0xcafe)]
+ #[expected_failure(abort_code = 0x50002, location = aptos_framework::permissioned_signer)]
+ fun test_auth_1(
+ creator: &signer,
+ ) {
+ let aptos_framework = create_signer_for_test(@0x1);
+ timestamp::set_time_has_started_for_testing(&aptos_framework);
+
+ let perm_handle = permissioned_signer::create_permissioned_handle(creator);
+ let perm_signer = permissioned_signer::signer_from_permissioned(&perm_handle);
+
+ permissioned_signer::authorize(&perm_signer, &perm_signer, 100, OnePermission {});
+ permissioned_signer::destroy_permissioned_handle(perm_handle);
+ }
+
+ #[test(creator = @0xcafe)]
+ #[expected_failure(abort_code = 0x50002, location = aptos_framework::permissioned_signer)]
+ fun test_auth_2(
+ creator: &signer,
+ ) {
+ permissioned_signer::authorize(creator, creator, 100, OnePermission {});
+ }
+
+ #[test(creator = @0xcafe, creator2 = @0xbeef)]
+ #[expected_failure(abort_code = 0x50002, location = aptos_framework::permissioned_signer)]
+ fun test_auth_3(
+ creator: &signer,
+ creator2: &signer,
+ ) {
+ let aptos_framework = create_signer_for_test(@0x1);
+ timestamp::set_time_has_started_for_testing(&aptos_framework);
+
+ let perm_handle = permissioned_signer::create_permissioned_handle(creator);
+ let perm_signer = permissioned_signer::signer_from_permissioned(&perm_handle);
+
+ permissioned_signer::authorize(creator2, &perm_signer, 100, OnePermission {});
+ permissioned_signer::destroy_permissioned_handle(perm_handle);
+ }
+
+ // Accessing capacity on a master signer
+ #[test(creator = @0xcafe)]
+ #[expected_failure(abort_code = 0x50003, location = aptos_framework::permissioned_signer)]
+ fun test_invalid_capacity(
+ creator: &signer,
+ ) {
+ permissioned_signer::capacity(creator, OnePermission {});
+ }
+
+ // creating permission using a permissioned signer
+ #[test(creator = @0xcafe)]
+ #[expected_failure(abort_code = 0x50001, location = aptos_framework::permissioned_signer)]
+ fun test_invalid_creation(
+ creator: &signer,
+ ) {
+ let aptos_framework = create_signer_for_test(@0x1);
+ timestamp::set_time_has_started_for_testing(&aptos_framework);
+
+ let perm_handle = permissioned_signer::create_permissioned_handle(creator);
+ let perm_signer = permissioned_signer::signer_from_permissioned(&perm_handle);
+
+ let perm_handle_2 = permissioned_signer::create_permissioned_handle(&perm_signer);
+ permissioned_signer::destroy_permissioned_handle(perm_handle);
+ permissioned_signer::destroy_permissioned_handle(perm_handle_2);
+ }
+
+ #[test(creator = @0xcafe)]
+ fun test_permission_revokation_success(
+ creator: &signer,
+ ) {
+ let aptos_framework = create_signer_for_test(@0x1);
+ timestamp::set_time_has_started_for_testing(&aptos_framework);
+
+ let perm_handle = permissioned_signer::create_storable_permissioned_handle(creator, 60);
+ let _perm_signer = permissioned_signer::signer_from_storable_permissioned(&perm_handle);
+
+ permissioned_signer::revoke_permission_handle(creator, permissioned_signer::permission_address(&perm_handle));
+
+ permissioned_signer::destroy_storable_permissioned_handle(perm_handle);
+ }
+
+ #[test(creator = @0xcafe)]
+ #[expected_failure(abort_code = 0x50007, location = aptos_framework::permissioned_signer)]
+ fun test_permission_revokation_and_access(
+ creator: &signer,
+ ) {
+ let aptos_framework = create_signer_for_test(@0x1);
+ timestamp::set_time_has_started_for_testing(&aptos_framework);
+
+ let perm_handle = permissioned_signer::create_storable_permissioned_handle(creator, 60);
+ let _perm_signer = permissioned_signer::signer_from_storable_permissioned(&perm_handle);
+
+ permissioned_signer::revoke_permission_handle(creator, permissioned_signer::permission_address(&perm_handle));
+ let _perm_signer = permissioned_signer::signer_from_storable_permissioned(&perm_handle);
+
+ permissioned_signer::destroy_storable_permissioned_handle(perm_handle);
+ }
+}