bsips/bsip-0040.md
2018-08-02 21:55:41 +02:00

18 KiB

BSIP: 0040
Title: Custom active permissions
Authors:
  Alex Megalokonomos <https://github.com/clockworkgr>
  Fabian Schuh <https://github.com/xeroc>
  Stefan Schießl <https://github.com/sschiessl-bcp>
Status: Draft
Type: Protocol
Created: 2018-07-25
Discussion: https://github.com/bitshares/bitshares-core/issues/1061
Worker: <Id of worker proposal>

Abstract

Strengthening user security is one of the main factors to elevate BitShares. In light of recent hacking and phishing attempts this becomes even more important. The need for a more sophisticated account security preceeded the idea for a finer-grained control of account permissions. We propose to add an additional authority to the account, called Custom Active (Permission). The permission contains a list of operationid-to-authority mappings that each grant access to the respective operation as if it were the active permission of the account. Additionally, the arguments of said operation can be restricted.

Motivation

Any successfull hacking or phishing attempt on any of the web wallets that are powered by the BitShares Blockchain is bad publicity. The user needs to be educated in account security, and this BSIP aims to ensure all technical possibilities are met while being flexible to allow many use-cases.

With this BSIP any user can create additional keys with specific purpose (everything else is prohibited). We list some possibilities below:

  • Witness Key: Only allows update signing key and publish price feed
  • Trading Key: Only allows limit orders (arguments restricted to desired markets), update margin position and transfers (arguments restricted to certain accounts)
  • Proposal Update Key: Approve proposals (2FA comes to mind)
  • Faucet Key: Allow only to create accounts
  • Withdrawal Key: Allow another account to transfer funds to himself
  • Cold Storage Key: Only allow to move funds to the Hot Wallet

The above list of named keys is nothing that is known to the backend as the backend should have an abstract implementation. The UI could provide a button "Create Trading Key" that properly configures the respective custom active permission entry.

Note: The user can still use the active authority just like used to, no change in existing behavior. The named keys only give an additional possibility. For example: The user can login the UI using the private key of such a named key. Then he only as access to the operations that this named key grants. He can still login with the existing password to gain full access (similar a local wallet can be created that only contains the named key).

This BSIP will be split into three parts that will be voted on separately (see Milestones section). All of the above keys are possible with Milestone 1. Milestone 2 allows stateful restrictions (e.g. allow market orders up to some amount for every month), Milestone 3 gives finer control of how to combine restrictions. Milestone 2 and 3 will be put up for voting if Milestone 1 proves successful.

Rational

Custom active permission is a list of custom active authorities. A custom active authority contains an operation_id, an authority (just like with active permission) and restrictions than can be used to restrict arguments and is only valid a certain time period (valid_from and valid_to). When handling incoming signed transactions, the backend checks for each operation if there is a custom active authority for any of its required accounts. Check for every required account of the transaction if all its belonging operations have at least one positively matched custom active authority (match means its authority is granted through present signatures, same operation_id, now is within valid_to and valid_from and none of the restrictions is violated), and if so grant the active authority of the corresponding account.

Specification

All descriptions in this section are on a pseudo/prosa level and no recommendation how it can best be implemented or serialized. They are meant to facilitate the understanding. If anything in the looping process or order of evaluation is unsuitable for actual implementation, changes can be made accordingly as long the same functionality is achieved.

Custom active permission and custom active authority

A custom active permission contains a list of custom active authorities and looks like follows (in JSON-like/pseudo for clarification):

custom_active_permission = {
    account_id,    // account that is assigned to this permission
    authorities = list of custom_active_authority objects
}
custom_active_authority = {
    enabled,       // status of this authority, true or false. true when created
    valid_from,    // timestamp when this is active
    valid_to,      // timestamp when this is invalid
    operation_id,   // operation id of the target operation,
    authority,     // same as for the existing authotities (multisig with weighted accounts or keys),
    restrictions        // see below
}

Note: This assumes custom_active_permission is stored in a separate index. Actual implementation details left to the implementer, as long as every custom_active_permission can be assigned to exactly one account.

A custom active authority is matched against operations of an incoming, signed transaction. The wording matching refers to:

  • operation_id is equal to the id of the incoming operation
  • account_id is in the required accounts of the operation
  • the authority of the custom_active_authority is given by the signatures of the transaction
  • timestamp now is within valid_to and valid_from
  • all restrictions assert positively

Restrictions

The restrictions field is a list of restrictions consisting of argument to assert mappings. A dictionary-type object like

restriction = {
   function,     // argument_identifier
   argument,     // pointer to a dynamic value (argument of the operation, or attribute when nested)
   data,         // data specific to the function
}

is called a restriction, that can assert itself positively (passed) or negatively (violated). All restrictions are evaluated per default with AND logic to determine if the whole list has asserted positively.

List of possible restrictions are:

function data state
any [list, of, allowed, values] stateless
none [none, of, these, values] stateless
lt, le, gt, ge comparative stateless
limit [max_cumsum, interval_in_sec] [current_cumsum, interval_began]
limit_monthly [max_cumsum, interval_in_months] [current_cumsum, interval_began]
attribute_assert list of restrictions stateless
logical_or list of restrictions lists stateless

Following cases must hold for a restriction:

  • if there is no value given (e.g. an optional argument, or nested value not given), the restrictions is passed (even if the underlying operation understands the absence of a key as instructions to delete it on-chain, see bitshares/bitshares-core#838)
  • if the expected type of the argument does not match the given type (no implicit type conversion), the restriction is violated
  • if the function of the restriction asserts negatively, the restriction is violated

Note:

  • If required a field can be added that stores the assumed type of the argument

In the following we list possible restrictions. Mentioning argument value in the text below refers to the value of the argument of the operation specified by argument of a restriction.

any

Stateless assert, all argument types. Argument value must be equal to one of values in the data list

none

Stateless assert, all argument types. Argument value must NOT be equal to any of the values in the list.

lt, le, gt, ge

Stateless assert. Allows explicit type conversion:

  • int type: use as is
  • string type: use length of string as argument value

The different asserts read as:

  • lt: Argument value must be less than comparative
  • le: Argument value must be less than or equal to comparative
  • gt: Argument value must be greater than comparative
  • ge: Argument value must be greater than or equal to comparative

limit

Stateful assert, only int type arguments. When the authority is created, interval_began is set to valid_from from its custom active authority and max_cumsum to 0. Incoming operations are first tried to match all stateless asserts, and if all passes continue with stateful asserts. If now > interval_began + interval_in_sec, then set max_cumsum = 0 and set interval_began = now. The assert that needs to pass is now current_cumsum + incoming value <= max_cumsum. If all asserts of this custom_active_authority pass, update current_cumsum = current_cumsum + incoming value.

limit_monthly

Stateful assert, only int type arguments. Analogue to limit, but interval_began is initially set to month(valid_from) and set to month(now) on update, additionally the time assert is month(now) >= interval_began + interval_in_months (include logic for month overflow when year changes). The month(now) call assumes zulu time to calculate month number.

attribute_assert

Stateless assert, only for dictionary type objects. The data list contains restrictions that all must pass, the reference for the argument of the child restriction is nested into the attributes of the parent dictionary type object. Allows nesting of attribute_assert.

logical_or

Stateless assert, only for dictionary type objects. The data is a list of restrictions lists, i.e. data = [ [restriction 1, restriction 2], [restriction 3, restriction 4], ... ]. If one of the restrictions sub-lists in data passes as whole, this restriction passes. In above miniexample that would mean if restriction 1 and restriction 2 pass, the whole logical_or restriction is considered to be passed as well.

Outline of handling incoming transactions

When a signed transaction arrives and before the backend evaluates if all necessary authorities are present through the signatures, do the following:

  • iterate over required accounts and for each account, iterate over all operations within the transactions that require the active authority of this account
  • iterate the custom_active_authorities of said account
  • if a custom_active_authority is found that matches , remember that and stop iterating the authorities and continue until all operations are checked
  • if the account has a custom active authority match for every operation in the transaction that requires it, then grant the active authority of said account. If no match is found, treat as if no authority was given

Note:

  • A custom_active_authority can only grant the active authority of the corresponding account, nothing more

Economics

Adding a custom active authority means increased effort for the backend, and with a stateful one also the need for more storage. Proposed transaction fees:

  • install_custom_active_authority: Tied to the duration of the custom active authority. Normal accounts can only create custom active authoritites with a duration of maximum 1 year. LTM can do any duration and also unlimited, but the transaction fee is capped at duration of 2 years.
  • update_custom_active_authority: Base fee similar to account_update plus dynamic one for any duration changes
  • delete_custom_active_authority: Cheap similar to limit_order_cancel

Modification to the backend

  • Add a new index or extend the account object to store custom active permission are assigned to an account and contain a list of custom active authorities. Multiple custom active authority entries are possible for one operation
  • If the active authority of the account is updated, all custom active authorities are disabled and must be enabled again with using update_custom_active_authority
  • Provide operations: install_custom_active_authority, update_custom_active_authority, delete_custom_active_authority to allow changing the custom active permission (3 operation to allow custom transaction fees and avoid having to send the complete list of all authorities for every update)
  • Operation-specific authorities (if present) must be evaluated in incoming transactions
  • Remove expired custom_active_authorities on maintenance
  • Additional committee parameters may be needed to limit the extend of usage of this feature

Notes: The implementation must not differentiate on which operation the custom active authority is applied, all operations are treated in same fashion

Discussion

To be found in the issue and pull request.

Examples

These examples are for illustration and no specification of actual serialization.

Example: Nested arguments like options

Assume asset_update_operation. All attributes of its options must be filled on update call. This assert can not be used to realize a "may only change attribute xzy of options". This would require that the logic knows which of the arguments are reflected on-chain and it knows how to query it for every operation that contains options. If options are to be restricted with this assert, all values that should not change would need be fixated by defining an any assert for those attributes, while having e.g. a lt assert for the one attribute that is allowed to change.

Example: Simple transfer

Assume account A and B and some unrelated key K. The custom active authority should be put that transfer transaction sending funds away from A can be signed with key K as long as the receiver is B. More concrete, the authority would look like

custom active authority = {
    valid_from: 7.7.2018 00:00
    valid_to: 8.7.2018 00:00
    operation_id: transfer,
    authority: {
       threshold: 1
       key_auth: [key K, 1]
       account_auth: []
    },
    restrictions: [
        {
            function: any,
            argument: to,
            data: [account B]
        } // this restricts the argument identified with "to"
    ]
}

Note: This is included with the first Milestone

Example: Either or

Assume account A, B and C and asset X and asset Y. The custom active authority should now achieve that a transfer transaction sending funds away from A can be signed with with active authority of account B if

  • it sends less than 10000 of asset X to account C
  • it sends less than or equal to 20000 of asset Y to account C

More concrete, the authority would look like

custom active authority = {
    valid_from: 7.7.2018 00:00
    valid_to: 8.7.2018 00:00
    operation_id: transfer,
    authority: {
       threshold: 1
       key_auth: []
       account_auth: [account B, 1]
    },
    restrictions: [  
        {
            function: logical_or,
            data: [ either_list, or_list ]
        }  
    ]
}

either_list =
[  
    {
	function: attribute_assert,
	argument: amount,
	data: [
	    {
	       function: lt,
	       argument: amount,
	       data: 10000
	    },
	    {  
	       function: any,
	       argument: asset_id,
	       data: [ asset X ]
	    }
	]
   },
   {  
       function: any,
       argument: to,
       data: [ account C ]
   }
]
	    
or_list =
[  
    {
	function: attribute_assert,
	argument: amount,
	data: [
	    {
	       function: le,
	       argument: amount,
	       data: 20000
	    },
	    {  
	       function: any,
	       argument: asset_id,
	       data: [ asset Y ]
	    }
	]
   },
   {  
       function: any,
       argument: to,
       data: [ account C ]
   }
]

Note: This is included with the third Milestone

Example: Checking for custom active authorities

Assume Account A, B and C. Now A has two custom active authorities:

  • custom active authority 1: Allow Account B to transfer asset X to D
  • custom active authority 2: Allow Account C to transfer asset X to D

The incoming transaction now contains transfer 100 asset X from A to D, signed by all signatures required for active authority of C. The required accounts (meaning required active authority) for the transaction is Account A. Backend would start considering custom active authority 1 and check if active authority of account B is present through signatures. It is not, thus continue by checking if authority of custom active authority 2 is present, which it is. Acive authority of Account A is granted and normal authority checks are continued. Since the required accounts is Account A, and the given accounts is also Account A through custom active authority 2, the transaction is executed.

Milestones

We propose do split the implmentation into two milestones. Each milestone will be voted on as a separate BSIP:

  1. Implementation of basic functionaliy to allow custom active permissions and authorities, including any, none and lt, le, gt, ge and attribute_assert asserts. If deemed necessary by developpers, reduce to only allow one key or one account for every custom active authority
  2. Implement stateful asserts limit and limit_monthly
  3. Implement logical_or

This approach allows as well to add other asserts at a later stage (with a new BSIP).

Summary for Shareholders

Bad publicity in terms of security can have very negative effect on the BTS value. This BSIP allows that traders can e.g. use a trading key, witnesses can use their witness key and a faucet can use a faucet key. If then for some reason the key or witness/faucet server becomes compromised, such a key can do little harm to the account holders, minimizing the risk.

This BSIP opens up a lot of use-cases as presented in Motivation section. The intention is to not alter any existing logic of the permission system, which reduces the risk of malfunctioning.

Copyright

This document is placed in the public domain.