bsips/bsip-0040.md

218 lines
14 KiB
Markdown
Raw Normal View History

2018-07-25 11:19:23 +00:00
BSIP: 0040
Title: Custom active permissions
Authors:
2018-07-25 12:11:50 +00:00
Alex Megalokonomos <https://github.com/clockworkgr>
2018-07-25 11:19:23 +00:00
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
2018-07-27 11:14:48 +00:00
Strengthening user security is one of the main factors to elevate BitShares. In light of recent
2018-07-25 11:19:23 +00:00
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.
2018-07-25 11:35:17 +00:00
# Motivation
2018-07-25 11:19:23 +00:00
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
2018-07-28 13:56:49 +00:00
aims to ensure all technical possibilities are met while being flexible to allow many use-cases.
2018-07-25 11:19:23 +00:00
2018-07-28 13:56:49 +00:00
With this BSIP any user can create keys with specific purpose (everything else is prohibited). We list some possibilities below:
2018-07-25 11:19:23 +00:00
- 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)
2018-07-25 11:34:40 +00:00
- 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
2018-07-25 11:19:23 +00:00
The above list of named keys is nothing that is known to the backend as the backend should have an abstract implementation.
2018-07-27 11:14:48 +00:00
The UI could provide a button "Create Trading Key" that properly configures the respective custom active permission entry.
2018-07-25 11:19:23 +00:00
2018-07-25 11:35:17 +00:00
# Rational
2018-07-25 11:19:23 +00:00
2018-07-28 22:01:37 +00:00
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 `asserts` 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 `operationid`, now is within `valid_to` and `valid_from` and all `asserts` pass), and if so grant the active authority of the corresponding account.
2018-07-25 12:11:03 +00:00
2018-07-28 13:43:41 +00:00
# Specification
2018-07-28 22:01:37 +00:00
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.
2018-07-28 13:43:41 +00:00
### Custom active permission and custom active authority
2018-07-27 13:54:30 +00:00
A `custom_active_permission` looks like follows (in JSON-like/pseudo for clarification):
2018-07-25 12:11:03 +00:00
```
custom_active_permission = list of custom_active_authority items
custom_active_authority = {
2018-07-28 14:03:14 +00:00
valid_from, // timestamp when this is active, defaults to now
2018-07-29 13:39:59 +00:00
valid_to, // timestamp when this is invalid, defaults to 1 year
2018-07-28 14:03:14 +00:00
operationid, // operationid of the target operation,
authority, // same as for the existing authortities (multisig with weighted accounts or keys),
asserts // see below
2018-07-25 12:11:03 +00:00
}
```
2018-07-28 14:03:14 +00:00
Note: This assumes `custom_active_permission` is stored within `account_object`. Actual implementation details left to the implementer, as long as every `custom_active_permission` can be assigned to exactly one account.
2018-07-25 11:19:23 +00:00
#### Wording
A `custom active permission` contains a list of `custom active authority`. `Custom active authority` can match an operation of an incoming, signed transaction. The wording *matching* refers to:
- `operationid` is equal to the id of the incoming operation
- assigned account of parent `custom active permission` is in the required accounts of the operation
- the `authority` of the `custom_active_authority` is given by the signatures of the transaction
- `now` is within `valid_to` and `valid_from`
- all `asserts` pass positively
2018-07-28 13:43:41 +00:00
### Asserts
2018-07-27 13:54:30 +00:00
The `asserts` is a list of `assert_objects` that are all together evaluated with `and` logic to evaluate a match
```
asserts = list of (argument_identifier, assert_object) tuples
argument_identifier = // target unknown, can be argument of operation, or in a nested dictionary like struct
assert_object = {
function, // functionid to do the assert
data, // stores data specific to the chosen function
state // if this assert is statefull
}
```
List of possible asserts are:
| function | data | state |
| ------------- |:-------------:| -----:|
| `any` | [`list`, `of`, `allowed`, `values`] | stateless |
2018-07-28 21:31:57 +00:00
| `none` | [`none`, `of`, `these`, `values`] | stateless |
| `lt, le, gt, ge` | `comparative` | stateless |
2018-07-27 13:54:30 +00:00
| `limit` | [`max_cumsum`, `interval_in_sec`] | [`current_cumsum`, `interval_began`] |
| `limit_monthly` | [`max_cumsum`, `interval_in_months`] | [`current_cumsum`, `interval_began`] |
2018-07-30 05:51:45 +00:00
| `attribute_assert` | `attribute_to_assert` = [(`attribute_identifier`, `any assert_objects`)] | stateless |
2018-07-27 13:54:30 +00:00
2018-07-29 05:58:18 +00:00
There is no implicit type conversion when attempting to assert, incompatible type means assert failure. If required, a field can be added that stores the assumed type of the argument (if conversion fails, assert fails). If an argument has no asset, it is unrestricted and may be given (or not for optional arguments) with any value.
2018-07-27 13:54:30 +00:00
In the following we list possible `asserts`. Mentioning `argument value` refers to the value of the argument of the operation specified `argument` in `assert_object`. The logic does not differentiate if an `argument` is optional or mandatory. All asserts imply: If the `argument` is given, it must pass the `assert`. If the `argument` is not given, assert is implicitly passed.
2018-07-28 21:31:57 +00:00
2018-07-27 13:54:30 +00:00
#### `any`
2018-07-28 21:31:57 +00:00
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.
2018-07-27 13:54:30 +00:00
2018-07-28 21:31:57 +00:00
#### `lt, le, gt, ge`
Stateless assert. Allows explicit type converstion:
- `int` type: use as is
- `string` type: user `length` as `argument value`
- `price` struct `{base = {amount, asset_id}, quote = {amount, asset_id}}`: convert to float price `argument value = base.amount/quote.amount`
The different asserts read as:
2018-07-28 21:43:28 +00:00
- `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`
2018-07-27 13:54:30 +00:00
#### `limit`
Statefull 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,
2018-07-27 13:57:24 +00:00
and if all passes continue with statefull 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 are passed, update `current_cumsum = current_cumsum + incoming value` of all involved statefull asserts.
2018-07-27 12:52:29 +00:00
2018-07-27 13:54:30 +00:00
#### `limit_monthly`
2018-07-27 13:57:24 +00:00
Statefull 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).
#### `attribute_assert`
2018-07-29 07:57:58 +00:00
Stateless assert, only for dictionary type objects. The `attribute_to_assert` list contains mappings between attributes and asserts that they must fulfill, if present in the dictionary. Allows nesting of `attribute_assert`.
Note:
- 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 a `any` assert for those attributes, while having e.g. a `lt` assert for the one attribute that is allowed to change.
2018-07-28 21:38:48 +00:00
2018-07-28 22:18:01 +00:00
#### Example: Simple transfer
2018-07-25 11:34:40 +00:00
Assume account A and B and some unrelated key K. Furthermore A has a custom active authority in the following way:
```
custom active authority = {
valid_from: 7.7.2018 00:00
valid_to: 8.7.2018 00:00
operationid: transfer,
2018-07-25 11:50:14 +00:00
authority: {
threshold: 1
key_auth: [K, 1]
account_auth: []
},
asserts: [
(to, {
function: any,
data: [B]
}) // this restricts the argument identified with "to"
]
2018-07-25 11:34:40 +00:00
}
```
2018-07-28 13:43:41 +00:00
That has the consquence now that a transfer transaction sending funds away from A can be signed with key K as long as the receiver is B.
2018-07-25 11:19:23 +00:00
2018-07-28 13:43:41 +00:00
Note: This is just an illustration of a possible serialization, not a specification of the serialized format.
2018-07-29 13:39:59 +00:00
### Economics
Adding a custom active authority means increased effort for the backend, and with a stateful one also the need for more storage.
We propose that the transaction fee for the creation is tied to the duration of the custom active authority.
Furthermore, 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.
2018-07-28 13:43:41 +00:00
### 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
2018-07-28 13:43:41 +00:00
- 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
2018-07-28 22:18:01 +00:00
#### Example: Checking for custom active authorities
2018-07-28 22:16:52 +00:00
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
2018-07-28 22:18:01 +00:00
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.
2018-07-28 22:16:52 +00:00
2018-07-28 13:43:41 +00:00
### Modification to the backend
* Extend the account object to store custom active permission that includes 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 need to be confirmed in the update. Every unconfirmed one is deleted otherwise
2018-07-25 12:11:03 +00:00
* Extend `account_update` or create a new operation to allow changing the custom active permission
* Operation-specific authorities (if present) must be evaluated in incoming transactions
* Additional committee parameters may be needed to limit the extend of usage of this feature
2018-07-25 11:50:14 +00:00
2018-07-28 13:43:41 +00:00
Notes: The implementation must not differentiate on which operation the custom active authority is applied, all operations are treated in same fashion
# Milestones
2018-07-29 13:39:59 +00:00
We propose do split the implmentation into two milestones. Each milestone will be voted on as a separate BSIP:
2018-07-29 06:07:44 +00:00
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. Evaluation of stateful asserts `limit` and `limit_monthly` in terms of performance. If positively evaluated, implement
This approach allows as well to add other asserts at a later stage (with a new BSIP).
2018-07-25 11:19:23 +00:00
# Discussion
2018-07-28 14:03:49 +00:00
To be found in the [issue](https://github.com/bitshares/bitshares-core/issues/1061) and [pull request](https://github.com/bitshares/bsips/pull/86).
2018-07-25 11:19:23 +00:00
# Summary for Shareholders
2018-07-25 11:34:40 +00:00
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.
2018-07-25 11:19:23 +00:00
# Copyright
This document is placed in the public domain.