Proposal to add recovery account and time-locked savings balance features to BitShares

in #bitshares7 years ago (edited)

I think it is time for BitShares to learn from its younger sibling Steem and adopt some crucial security features: time-locked savings and recovery accounts. These are features that I have quickly come to expect from blockchains, even though I am not aware of any blockchain other than Steem that currently has these important features active. I really feel uncomfortable having large amounts of wealth stored on a blockchain that doesn't provide such features, and I am sure I am not unique in feeling that way. This can hold back investment.

Why time-locked savings balances are a critical feature

Yes, we can use cold storage of private keys and that certainly has its place even with time-locked savings balances. But it also adds a lot of inconvenience when you actually want to spend from those balances, and the tools for securely doing that, i.e. without exposing your keys to a possibly compromised computer, are still (much to my disappointment even after several years) not available in a user-friendly way. This means that users will, for the sake of convenience, likely keep larger amounts of their crypto-wealth in hot wallets than is smart to do just to avoid the massive inconvenience.

Hardware wallets, while still a great and useful tool, also have their own issues.

First, although not the most important justification, it is harder to iterate with hardware wallets to support new useful features added to the blockchain than it is with software running on traditional devices (desktops, smartphones, etc.). In addition a hardware wallet like a Trezor has a very limited human interface which, while it may be suitable for simple transfers, can be incredibly challenging to design for in a way users would find tolerable in order to authenticate more sophisticated transactions (such as ones involving the various capabilities that already exist in BitShares today and even more so with Steem). Some of these operations may not be important enough to protect with a hardware wallet, but there can still be high-value transactions that don't fit well with the authentication workflows allowed by hardware wallets existing today. A more flexible and capable approach to adding security to these types of operations would be to use a security service provider that utilizes multisig (or really the powerful dynamic hierarchical multi-signature threshold permissions of BitShares and Steem) to protect your funds by denying suspicious transactions based on certain user-tunable heuristics unless further verification is provided by the user before authenticating those transactions (there can of course always be a relief valve by using the owner keys in cold storage to take back control of the account should the security service provider disappear or refuse to do their job).

Second, needing to plug in an extra device each time you want to spend from your instantaneously-spendable balances can be inconvenient and annoying. So users would likely keep a reasonable-sized amount for convenient spending in a hot wallet with no hardware wallet protection, and just use the hardware wallet to protect their much larger amount of savings. This means that hardware wallets for the most part are useful for protecting funds that have liquidity demands more comparable to the funds expected to be used with a savings balance feature with time delays. And savings balances have better security properties against certain threats than hardware wallets (as will be discussed shortly by the third justification), so between the two of them I would prefer using savings balances rather than hardware wallets to secure most of my wealth. Of course, you can also just use both at the same time by, for example, keeping the cryptocurrency in a savings balance of a separate more secure account whose active keys are managed by a hardware wallet.

Source: xkcd
Note: I really enjoy xkcd comics. Check it out if you haven't already. Don't forget the hover text. Although in the case of this particular comic, I would say his claims of "actual actual reality" are soon becoming obsolete for more and more people due to adoption of cryptocurrencies and how attractive they are to criminals trying to steal them.

But there is an even more important consideration that shows why hardware wallets aren't enough and why time-locked savings really are needed. Imagine the scenario of a home invader seeing your Trezor, putting a gun to your head, and demanding that you unlock it to make a transfer of all your cryptocurrency to him. Are you really going to say no?

Time-locked savings and recovery accounts provides huge peace of mind since you can know that a sudden compromise of your private keys, whether by hacking or rubber-hose cryptanalysis (see relevant xkcd comic to the right) does not need to mean that all of your crypto-wealth will be instantly and permanently stolen.

Proposal for BitShares

The recovery account feature that I think BitShares should adopt is pretty straightforward. Just copy Steem's mechanism. You set a recovery_account (which would be any other BitShares account). Changing the recovery_account would be an operation that takes 30 days to complete and it could be canceled by the owner authority at any time in those 30 days. With a recovery account set, the user (in conjunction with the recovery partner) could use an owner authority that was active in the last 30 days to replace their account's owner authority.

Time-locked savings are needed to protect the user's wealth from being instantaneously stolen as soon as their private keys are compromised. Only a small portion of their wealth should be kept in a form that can be instantaneously spent; this includes (but is not limited to) assets in what is currently the only available place to simply hold balance in BitShares (which I will going forward call their checking balances), assets held in escrow contracts, assets in the order books of the DEX, and collateral assets held in short positions (although unlocking all of this collateral would still require paying off the debt of the short position of course).

Time-locked savings even without the recovery account feature are incredibly useful, because normally, if recommended security practices are followed, the owner keys of the account will be offline, and so a hacker compromising the account would likely only have active keys. They would then most likely immediately change their active keys, steal the checking balances (and other liquid balances, e.g. in order books), and initiate the savings withdrawals of all the savings balances to their account. However, the legitimate user could then bring out their owner keys (hopefully on an offline computer, or at least a freshly re-installed clean computer rather than their regular one that was likely hacked) to change the active keys and to cancel all the savings withdrawals. However, time-locked savings coupled with recovery accounts provide even greater security to the user, because even if their owner keys are also compromised, they can still work with their recovery partner to get back access to their account and hopefully they can do this in time to cancel the savings withdrawals.

Steem currently has one savings balance (however both STEEM and SBD assets can utilize that single savings balance) that enforces a fixed 3 day delay on savings withdrawals. However, if I was building a time-locked savings feature today, I would want to provide users with more flexibility than that. There are some balances that I would like to be protected for longer than 3 days for extra peace of mind, while for other smaller amounts 1 day is sufficient protection against hacks and it allows for quicker access to funds if I need it for a larger than normal transaction (or likely selling in the market to catch a good pump). Users should be able to make their own decisions on the trade-offs between security and liquidity, and they should be able to do it in easy-to-use ways that don't require them to run bots on servers that have access to their active keys (which itself causes security risk). Below I will describe the design for the time-locked savings feature that I would want BitShares (and frankly all other blockchains) to adopt.

Details of the proposed design for time-locked savings

I am going to use some of the implementation specific details of Graphene objects and operations to describe the design.

First, a brief background to those unfamiliar with the Graphene code base: the objects (ending in _object) are data structures that are kept in the database state and are created, modified, and destroyed by the evaluation of operations (ending with _operation), which are included in the actual blockchain, as well as by the evaluation of code (which I will refer to as triggers) that may potentially run every block to act on pending actions that were scheduled by prior operations to run at future times. In order to schedule actions for future times, an operation would need to create an appropriate object in the database containing the timestamp of when the system should trigger the action. This timestamp field in the object that determines when the action should be triggered may typically be given a name such as effective_on.

With all that said, I will still be using pseudocode and leaving off some of the details that actual make them legitimate code in the BitShares (or for that matter Steem) code bases. For example, in the pseudocode the structs for operations will not include the fields and methods relevant for tracking the fees that need to be paid for the operation, or other unimportant details such as the validate() declaration, the extensions field, or inheriting from base_operation for the sake of clarity. Furthermore, rather than provide code or pseudocode for the implementation of validate() or the operation's evaluator, I will simply describe the restrictions on the operation's values as well as how it works through prose and comments in the pseudocode for the struct definition. Similarly, in the pseudocode defining the classes for the objects I will not bother with unimportant details like inheriting the appropriate class, defining the space_id and type_id, or declaring/defining all helper methods relevant to the class. And rather than defining the the necessary indices, these can be inferred from the comments and by what would be necessary to carry out the triggers and operation evaluators described in this section in prose.

One last thing to mention before getting into the technical details is that this proposal only covers the savings balance feature for public balances. All funds held in these savings balances will be publicly visible to all (as typical usage of checking balances in BitShares today does). However, the BitShares blockchain does currently support blinded balances using the technology of Confidential Transactions (CT), even though most people do not use it because there is no user-friendly (and safe in terms of backing up the necessary keys to not lose funds) GUI interface to manage it. Those blinded balances would not be able to use the savings feature described in this proposal unless they were first unblinded into the checking balance. It should be possible to later augment the savings balance feature by providing support for blinded savings balances as well, but there are a lot of complications to work through with that design to preserve the safety guarantees of savings balances in the event of a hack and to reduce the likelihood that users lose access to funds due to improper private key backup particular when changing keys. At that later point in time when such an enhancement is being considered, I would also desire to use Confidential Assets (CA) on BitShares rather than CT, since CA would also blind the asset type in addition to the amount.

New objects

This proposal would introduce 3 new objects to BitShares: account_savings_balance_object, savings_withdraw_object, and savings_reduce_delay_object. The pseudocode for the objects is given below without much explanation. This is to introduce the objects and their fields which is necessary to better explain how the new operations work in the next sub-section. In addition, the account_object would be modified to include two new uint16_t fields called savings_balances_counter and pending_savings_adjustments_counter(both initialized to 0). These two counters will be needed to limit the number of Graphene objects that can be created per account for the purposes of this savings feature in order to prevent abuse of the memory usage of the Graphene object database. The limits would be hardcoded (although I suppose they could be committee parameters) and would require introducing two new constants: GRAPHENE_MAX_SAVINGS_BALANCES and GRAPHENE_MAX_PENDING_SAVINGS_ADJUSTMENTS. It is important to make sure GRAPHENE_MAX_SAVINGS_BALANCES is generously large because even if a single withdrawal delay was used, each unique asset type in the savings balance would count as its own savings balance object.

Pseudocode for account_savings_balance_object:

class account_savings_balance_object
{
public:
    account_id_type owner;  
    asset_id_type   asset_type;
    share_type      amount;
    uint32_t        delay = 0; // Withdrawal delay, in seconds.
    uint16_t        num_pending_withdrawals_originating_from_here = 0; 

    asset get_balance()const { return asset(amount, asset_type); }
    void  adjust_balance(const asset& delta); // Adds delta.amount to amount if delta.asset_id == asset_type
};

An account_saving_balance_object is uniquely determined by the tuple (owner, asset_type, delay).

To aid with the explanations of the new operations, it will be very helpful to first define the helper functions adjust_savings_balance and clear_unneeded_savings_balance defined below:

void database::adjust_savings_balance( account_id_type account, asset delta, uint32_t delay )
{ try {
    if( delta.amount == 0)
        return;
    
    auto& index = get_index_type<account_savings_balance_index>().indices().get<by_account_asset_delay>();
    auto itr = index.find(boost::make_tuple(account, delta.asset_id, delay));
    
    if( delta.amount < 0 )
    {
        FC_ASSERT( itr, "No such savings balance exists" );
        FC_ASSERT( itr->get_balance() >= -delta, "Insufficient savings balance" );
        
        if( itr->get_balance() != -delta )
        {
            modify(*itr, [&delta](account_savings_balance_object& b) {
                b.adjust_balance(delta);
            });
        } 
        else if( itr->num_pending_withdrawals_originating_from_here == 0 )
        {
            remove(*itr);
            modify(account(*this), [](account_object& a) {
                a.savings_balances_counter--;
            });
        }
    }
    else // delta.amount > 0
    {        
        if(itr == index.end())
        {
            auto& acnt = account(*this);
            FC_ASSERT( acnt.savings_balances_counter < GRAPHENE_MAX_SAVINGS_BALANCES, "Too many savings balances already exist.");

            create<account_savings_balance_object>([account,delay,&delta](account_savings_balance_object& b) {
             b.owner = account;
             b.asset_type = delta.asset_id;
             b.amount = delta.amount;
             b.delay = delay;
            });
            modify(acnt, [](account_object& a) {
                a.savings_balances_counter++;
            });
        }
        else 
        {
          modify(*itr, [&delta](account_savings_balance_object& b) {
             b.adjust_balance(delta);
          });
        }
    }
} FC_CAPTURE_AND_RETHROW( (account)(delta)(delay) ) }

bool database::clear_unneeded_savings_balance( account_id_type account, asset_id_type asset_type, uint32_t delay )
{ try {
    auto& index = get_index_type<account_savings_balance_index>().indices().get<by_account_asset_delay>();
    auto itr = index.find(boost::make_tuple(account, asset_type, delay));
    if( itr != index.end() && itr->amount == 0 && itr->num_pending_withdrawals_originating_from_here == 0 )
    {
        remove(*itr);
        modify(account(*this), [](account_object& a) {
            a.savings_balances_counter--;
        });
        return true;
    }
    return false;
} FC_CAPTURE_AND_RETHROW( (account)(asset_type)(delay) ) }

Pseudocode for savings_withdraw_object:

class savings_withdraw_object
{
public:
    account_id_type     from;
    account_id_type     to;
    uint32_t            request_id = 0;
    uint32_t            delay = 0; // Must be set to the delay of the savings balance this withdrawal was initiated from.
    asset               balance;
    optional<memo_data> memo;
    time_point_sec      effective_on;
};

A savings_withdraw_object is uniquely determined by the tuple (from, request_id). Furthermore, when creating a new savings_withdraw_object it is important to ensure that there is not already a savings_reduce_delay_object that both has an account field equal to the from field of the savings_withdraw_object and a request_id field equal to the request_id field of the savings_withdraw_object(see definition of savings_reduce_delay_object below).

As soon as the head_block_time is equal to or exceeds the effective_on time, the system will trigger an action which will first add balance to the checking account of to, then decrement the num_pending_withdrawals_originating_from_here field of the account_saving_balance_object identified by the tuple (from, balance.asset_id, delay), then call clear_unneeded_savings_balance(from, balance.asset_id, delay), then decrement the pending_savings_adjustments_counter of the from account (which counts the total savings_withdraw_objects and savings_reduce_delay_objects associated with the account), and finally destroy the savings_withdraw_object.

Pseudocode for savings_reduce_delay_object:

class savings_reduce_delay_object
{
public:
    account_id_type     account;
    uint32_t            request_id = 0;
    uint32_t            original_delay = 0; // Must be set to the delay of the savings balance this withdrawal was initiated from.
    uint32_t            new_delay = 0; // Should always be less than original_delay since increasing the delay is an instantaneous operation.
    asset               balance;
    time_point_sec      effective_on;
};

A savings_reduce_delay_object is uniquely determined by the tuple (account, request_id). Furthermore, when creating a new savings_reduce_delay_object it is important to ensure that there is not already a savings_withdraw_object that both has a from field equal to the account field of the savings_reduce_delay_object and a request_id field equal to the request_id field of the savings_reduce_delay_object.

As soon as the head_block_time is equal to or exceeds the effective_on time, the system will trigger an action which will first decrement the num_pending_withdrawals_originating_from_here field of the account_saving_balance_object identified by the tuple (from, balance.asset_id, original_delay). The system will then check to see if the savings_balances_counter field of the account is less than GRAPHENE_MAX_SAVINGS_BALANCES. If so it will call clear_unneeded_savings_balance(account, balance.asset_id, original_delay) and then call adjust_savings_balance(account, balance, new_delay); but if not, it will instead simply call adjust_savings_balance(account, balance, original_delay). Finally, in either case, it will finish up by decrementing the pending_savings_adjustments_counter field of the account and destroying the savings_reduce_delay_object.

New operations

This proposal would introduce 4 new operations to BitShares: deposit_to_savings_operation, withdraw_from_savings_operation, change_savings_delay_operation, and cancel_pending_savings_adjustment_operation.

The deposit_to_savings_operation allows a user to move some amount of any asset they have in their checking balance to their savings balance. Unlike Steem, this design would only allow a user to deposit in their own savings balances rather than that of another user. Personally, I don't like the idea (and I hope this is something that can eventually be opted out of in Steem) of a non-approved user depositing value under my control that I cannot quickly undo (especially when you consider the complications that may impose to tax accounting).

Since deposit_to_savings_operation is always instantaneous, one could emulate the behavior of depositing to someone else's savings account "directly" by creating a transaction that consists of two operations: first transferring the asset to the receiver's checking account and then immediately transferring the received asset to the receiver's savings balance using the deposit_to_savings_operation. This would create an atomic transaction that not only needs approval from the sender's active authority but also the receiver's active authority. Thanks to the proposed transaction feature of BitShares, signing this transaction by both parties can be done easily without requiring out-of-band communication and coordination. Doing things this way means that unsolicited transfers to a user's savings balance are disallowed, but it is still possible to securely move large funds (say a biweekly salary) from a sender's checking balance to a receiver's savings balance, if the receiver also approves, without exposing those funds to the checking balance in a way that can be suddenly compromised by a hacker who has access to the receiver's active private keys and is simply biding their time to use it when it becomes worth it.

It would not be possible to make a withdrawal from one user's savings balance to another user's saving balance directly (without exposing it to someone's checking balance for at least some brief period of time) unless more sophisticated mechanisms involving general delayed transactions were added to BitShares. (Keep in mind this is also not currently possible in Steem.) It may be worth considering building a more general delayed transaction mechanism as well. Perhaps with such a mechanism this savings balance feature could be redesigned to utilize it and provide even more flexibility for users. However, I don't think the value of a direct savings to savings transfer feature is so great compared to alternative ways of securing such a transfer process for it to be worth delaying the development of a time-locked savings feature (which would be very useful to so many users right now) until such a general system would be designed and implemented in order to support more unusual use cases.

Here is the pseudocode for the deposit_to_savings_operation:

struct deposit_to_savings_operation
{
    /// Account for which to move assets from checking to savings. Require active authority of this account.
    account_id_type     account;  
    /// The amount (balance.amount which must be positive) of a particular asset (balance.asset_id) to move from account's checking balance to savings balance.
    asset               balance;   
    /// Withdrawal delay, in seconds. Must be greater than 0 but less than 2592000 (30 days).
    uint32_t            delay = 0;    
};

This operation instantaneously moves the balance asset from the checking balance of account to an account_savings_balance_object by first deducting balance from the checking balance of the account and then calling adjust_savings_balance(account, balance, delay).

To withdraw balances from the savings balance to a checking balance (either that of the same account or another account), the withdraw_from_savings_operation must be used. Here is the pseudocode for the withdraw_from_savings_operation:

struct withdraw_from_savings_operation
{
    /// Account from which to withdraw assets from the savings balance. Require active authority of this account.
    account_id_type     from;  
    /// Account whose checking balance is where this withdrawal will eventually deposit the withdrawn assets to if the pending operation goes through.
    account_id_type     to;
    /// Unique (for the from account) request ID for this withdrawal. Clients will usually just use the current timestamp.
    uint32_t            request_id = 0;
    /// The amount (balance.amount which must be positive) of a particular asset (balance.asset_id) to withdraw from from's savings balance.
    asset               balance;   
    /// The withdrawal delay which completes the unique identification of the specific savings balance from which the withdrawal will be made.
    uint32_t            delay = 0;
    /// An optional memo that may particularly be useful if this withdrawal is going to pay a to account different than the from account.
    optional<memo_data> memo; 
};

This operation first increments the num_pending_withdrawals_originating_from_here field of the account_saving_balance_object identified by the tuple (from, balance.asset_id, delay), then calls adjust_savings_balance(from, -balance, delay), then increments the pending_savings_adjustments_counter of the from account (if value of that counter was not less than GRAPHENE_MAX_PENDING_SAVINGS_ADJUSTMENTS prior to incrementing then this operation fails), and finally it creates a new savings_withdraw_object where it simply copies over the similarly named fields from the withdraw_from_savings_operation and sets the remaining effective_on field to head_block_time + delay. This operation will fail if a savings_withdraw_object already exists with its from field equal to this operation's from field and its request_id field equal to this operation's request_id field. This operation will also fail if a savings_reduce_delay_object already exists with its account field equal to this operation's from field and its request_id field equal to this operation's request_id field.

The change_savings_delay_operation allows a user to make adjustments to the savings balances to either increase or decrease the withdrawal delays of some of their savings balances. It is possible to instantaneously increase the withdrawal delay of any of the savings balances, however decreasing the withdrawal delays is a pending (and cancellable) operation that has a delay much like a withdrawal. In either case, the end result would be that the balances still remain in the savings balance of the account in some form. Here is the pseudocode for the change_savings_delay_operation:

struct change_savings_delay_operation
{
    /// Account for which to change withdrawal delays of some of their saving balances. Require active authority of this account.
    account_id_type     account;  
    /// Unique (for the given account) request ID for this withdrawal delay change. Clients will usually just use the current timestamp. If new_delay > original_delay, then request_id will be ignored and can just be left as 0.
    uint32_t            request_id = 0;
    /// The amount (balance.amount which must be positive) of a particular asset (balance.asset_id) to put in a savings balance with the new specified withdrawal delay.
    asset               balance;   
    /// The withdrawal delay which completes the unique identification of which specific savings balance this operation will be applied to.
    uint32_t            original_delay = 0;
    /// The new withdrawal delay for the specified savings balance, which must be greater than 0 but less than 2592000 (30 days) and also cannot equal original_delay. 
    uint32_t            new_delay = 0;
};

This operation has two different behaviors depending on whether new_delay > original_delay or new_delay < original_delay.

Let us first consider the case where new_delay > original_delay. In this case, the operation can be carried out instantaneously. The system simply calls adjust_savings_balance(from, -balance, original_delay) and then calls adjust_savings_balance(from, balance, new_delay).

The second case where new_delay < original_delay is more complicated. The system first increments the num_pending_withdrawals_originating_from_here field of the account_saving_balance_object identified by the tuple (account, balance.asset_id, original_delay), then calls adjust_savings_balance(from, -balance, original_delay), then increments the pending_savings_adjustments_counter of the account (if value of that counter was not less than GRAPHENE_MAX_PENDING_SAVINGS_ADJUSTMENTS prior to incrementing then this operation fails), and finally it creates a new savings_reduce_delay_object where it simply copies over the similarly named fields from the change_savings_delay_operation and sets the remaining effective_on field to head_block_time + original_delay - new_delay. This operation will fail if a savings_withdraw_object already exists with its from field equal to this operation's account field and its request_id field equal to this operation's request_id field. This operation will also fail if a savings_reduce_delay_object already exists with its account field equal to this operation's account field and its request_id field equal to this operation's request_id field.

Finally, the cancel_pending_savings_adjustment_operation allows the user to cancel any of the pending actions related to savings, whether it is a pending withdrawal or a pending reduction in some saving balance's withdrawal delay. Here is the pseudocode for the cancel_pending_savings_adjustment_operation:

struct cancel_pending_savings_adjustment_operation
{
    /// Account for which to cancel pending savings adjustment. Require active authority of this account.
    account_id_type     account;  
    /// Unique (for the given account) request ID for the pending savings adjustment to cancel.
    uint32_t            request_id = 0;
};

This operation first tries to find a savings_withdraw_object that has a from field equal to this operation's account field and a request_id field equal to this operation's request_id field. If it cannot find such an object, then it tries to find a savings_reduce_delay_object that has an account field equal to this operation's account field and a request_id field equal to this operation's request_id field. If it cannot find that either, then the operation fails.

If the system found the savings_withdraw_object then using its from, delay, and balance fields it will first decrement the num_pending_withdrawals_originating_from_here field of the account_saving_balance_object identified by the tuple (from, balance.asset_id, delay), then call adjust_savings_balance(from, balance, delay), then decrement the pending_savings_adjustments_counter of the from account, and finally destroy the found savings_withdraw_object.

On the other hand, if the system found the savings_reduce_delay_object then using its account, original_delay, and balance fields it will first decrement the num_pending_withdrawals_originating_from_here field of the account_saving_balance_object identified by the tuple (account, balance.asset_id, original_delay), then call adjust_savings_balance(account, balance, original_delay), then decrement the pending_savings_adjustments_counter of the account, and finally destroy the found savings_reduce_delay_object.

Conclusion

The time-locked savings feature described above goes beyond what is currently available in Steem by giving the user the flexibility to make the trade-off between security (which requires longer delays to give the user enough time to recover access to their account and cancel pending withdrawals before losing any funds) and liquidity (which is also highly desirable since the user does not want to wait too long to legitimately use their own money). This time-locked savings feature coupled with the recovery account feature (the same one already enjoyed on the Steem blockchain) could allow the BitShares blockchain to provide users with much greater security of funds than currently exists on BitShares, and in fact can provide greater security against certain threats involving coercion than even hardware wallets can provide.

The design described above puts a hardcoded limit of 30 days for the maximum allowable withdrawal delay for a savings balance. This is because the account recovery feature would no longer work 30 days after owner authority compromise of an account, so having longer savings withdrawal delays than 30 days would not mean very much to the user if their entire account has already been permanently stolen away from them. While savings withdrawal delays greater than 30 days may perhaps still be useful for some people against threats where only the active authority was compromised (and not owner authority), the utility of such long delays seems questionable, and putting a reasonable limit such as 30 days means the system can allow increasing the withdrawal delays of existing savings balances instantaneously using the change_savings_delay_operation without putting the user at too much risk of a trollish hacker who realizes they probably will not be able to steal the victim's savings so instead punishes them out of frustration by pushing all the victim's savings withdrawal delays to their maximum allowable amounts.

And being able to instantaneously increase the withdrawal delays of one's savings using their active authority could be a useful security feature to have. For example, a user finding themselves in a very precarious position may wish to have a "panic button" which escalates the security level of their savings, which are normally (by choice of the user) kept 30% in a 2 day delay and 70% in a 2 week delay, to a high-alert level in which 10% of their savings become secured with a 1 week delay and the remaining 90% secured with a 3 week delay. Let us say things went wrong for the user as he feared and he was abducted, but fortunately after one and a half weeks law enforcement were able to rescue the user. In such a scenario, the abductors may have gotten away with stealing the user's checking balance and the 10% of his savings that was protected by the 1 week delay. However, had the user not pressed the panic button in time, the abductors may have gotten away with the user's checking balance and 30% of his savings; and if the law enforcement had taken nearly one additional week to rescue the user, it could have been 100% of his savings that would have been stolen had he not pressed the panic button. If the user's fears were unwarranted and it turned out to be a false alarm but the user pressed the panic button anyway, the user could then use change_savings_delay_operation to eventually return back to his normal security level settings without ever exposing those savings funds to the checking balance of his account. In the case of this example: 5 days after initiating the delay change transaction, the user would have 10% of his savings protected by a 2 day delay, and 90% in flux but ultimately protected by a 16 day delay; then, 7 days after initiating the delay change transaction, the user would have 10% of his savings protected by a 2 day delay, 70% of his savings protected by a 2 week delay; and 20% of his savings in flux but ultimately protected by a 2 week delay; and finally, 19 days after initiating the delay change transaction, the user would recover his original normal security level settings which is 70% of his savings protected by a 2 week delay and 30% protected by a 2 day delay.

With the above features users would be able to keep most of their funds in savings balances chosen to meet their personal trade-off of security versus liquidity/convenience, and they could keep the amount of their wealth kept in instantaneously-spendable balances to a minimum allowing them to have peace of mind knowing that even if they are hacked or extorted to give up passwords/keys by criminals (at least during a short interaction / abduction relative to their withdrawal delay settings) they won't have to end up financially devastated. Further protection of even their checking balances or other instantaneously-spendable balances against many threats could then also be provided through other means such as multisig security service providers, but that goes beyond the scope of this proposal.

Sort:  

Are you going to make a worker proposal? I would be happy to vote for it. With such a detailed proposal this sounds viable, and I would be surprised if it didn't get enough support. Even anti-dilution people should care about their holdings...

This proposal is for the core, but it should be taken care of in the GUI as well, shouldn't it?

sounds great, as far as I was able to understand it. can you implement?

Badass proposal, and I back this 100%. Resteemed. You rock @arhag

The bitshares dex is the goldmine of crypto! Make it better and I'm all for it!

Another thing, @arhag in regards to security. I would very much like a closed wallet where no one can see my funds. For the man with the gun I'm not of much interest, since it at present is a few hundred dollars. But it is very easy for the man with the gun to find who would be worth extorting.

Second, I would really like stealth features on par with the most secure currencies.

Yes, I think we all would like that. I have no idea what is going on with or what the current status is of stealth/privacy features development for BitShares (there seems to be some controversy / non-technical issues happening slowing progress down there). I'll just speak about it from a purely technical perspective based on what I know.

When we bring blinded balances in the mix, we need to be very careful with the design of time-locked savings to ensure that an operation (seemingly coming from the user with active authority) which appears to just keep their funds in their control does not actually re-encrypt things in a way to make the secrets necessary to unblind the balances inaccessible to the user. (This is not really an issue for checking balances, because if an attacker ever got authority to spend those they could just instantaneously steal them anyway.) In other words, we need to not only consider threats in which a hacker is trying to steal funds, but also threats in which a vindictive hacker simply destroys the user's funds because they cannot steal them. Short of some advanced cryptography that I am not aware of, the way to prevent against this threat in time-locked savings balances is to require any merging of existing blinded savings balance outputs, either to clean up balance object usage or to collect together enough funds to make a withdrawal of a desired amount either to checking or to a savings balance with another withdrawal delay (whether lower or higher), to be a reversible delayed action with the same delay requirements as a straightforward withdrawal of it to checking. In short, it is just trickier to work out the safe design of time-locked savings when blinded balances are involved compared to public balances. But it is absolutely doable, worth doing, and worth doing right.

There are other complications worth noting about blinding balances.

First, if you blind BTS balances that BTS will not contribute to your stake when voting on witnesses, committee members, and workers. (Side note: although not mentioned in the proposal in the OP, I assumed that all public BTS balances held in savings would add up along with the checking balance towards the account's BTS voting stake.) Having less BTS stake voting, since a lot of it would likely be hidden by users for the sake of their privacy, is not great for the network, but I'm not sure if there is any solution around that if users wish to truly and very securely hide the amount of their BTS holdings. I've previously thought about solutions that allow the user to expose some chosen amount of just their BTS balances to a trusted third party (acting as their proxy) who aggregates the BTS amounts revealed to them by various users before revealing that aggregate amount publicly to the network (the privacy comes from the hope that enough individuals will be revealing their BTS balances to this proxy so that they can hide in the crowd). However, this system also has its issues. First, it puts limitations on how quickly you can spend the BTS balances that you have chosen to reveal to the proxy because those blinded amounts need to be locked up for a period of time otherwise this mechanism can be used to falsify the amount of BTS someone has for voting. Second, although users wouldn't want the lock-up period to be too high (for liquidity/convenience reasons), having too small of a window could harm their privacy. If they are the only one to, for example, stop revealing their balance to a particular proxy during a particular lock-up period, then the public could look at the delta between the two consecutive lock-up periods of the aggregate amount exposed by the proxy in order to determine a likely good estimate for how much BTS that user had. So, all of this complication just to allow some blinded BTS balances to be counted in proxy votes may just end up being an illusion of privacy in practice, which means it very likely isn't worth developing.

Second, losing access to your memo key (which is needed to derive the secrets to unblind your balances) means you will lose access to all your blinded funds. It doesn't matter if the network recognizes that your account technically owns them if you have no way of generating the cryptographic proofs to actually spend them. This is what makes blinded balances so dangerous. It is imperative to have a robust memo key backup procedure (such that the current memo key and public blockchain state can derive all past memo keys of the account) so that users do not end up losing access to funds when they change their private keys and realize they lost backups of their old one. Always using deterministically generated keys from a brain key (and safely backing up that brain key) helps with this. But if your brain key is compromised (and you still recover your account using the recovery account feature), you will need to use a new brain key, which cannot itself generate the old keys. So it is desirable to still have a robust backup mechanism to encrypt the most recent memo private key of the account known to the user on the blockchain in a way that is accessible using the current private memo key.

Third, despite all these complications, I think the effort required to build out the features to allow blinded balances using CA held in both checking and savings of an account (and of course the ability to do blinded transfers to other accounts using CA) is still pretty reasonable, at least on the core level (more effort is required to bring it to the GUI). But this provides financial privacy in the form of hiding how much of which assets you possess and also hides the amount and asset type being transferred from one account to another. It does not, however, also hide the metadata of the sender and receiver accounts that made some transfer at a specific time; that would still be publicly visible. This may be acceptable financial privacy to many, but unacceptable to others. To do better (hiding the metadata as well) would require even more effort and sophisticated design (especially if you still want it to be reasonably safe and easy-to-use for regular users), including the use of slightly more sophisticated cryptography like RingCT which Monero is using (or actually I would expect to see an adaptation of RingCT to use CA yielding a "RingCA", which I believe should be doable but I haven't yet looked into the details of how RingCT work). One thing to keep in mind is that we don't have to choose between them. There are advantages (in terms of lower time delays and cost) to just using plain CA and not hiding the metadata if you don't really care about that. Then those who really want more privacy can use the RingCA-based features that further try to obfuscate metadata. Assuming regular RingCA can be made to work with existing CA balance outputs without unblinding the amount / asset type in the process (which I think it can, but I don't know for sure), then it makes sense to first build out the regular CA support (not bothering to hide metadata yet) and then later build out the more advanced privacy features on top that further obfuscate metadata.

Haha... I hope you are aware half of that was way beyond my knowledge;-)

But you seem to understand whitch direction I think the software should be going from a end user perspective. The majority of the end users will not vote, don't have the knowledge to do it or simply don't care. A simple opt-out of this aspect of the network should be easy to do.

Another thing I have noticed is that I'm unable to make a backup when I am in account mode. Switching to wallet mode resolves this. But it is a pain in the rear end and I often end up not making a backup because of it.

I hope this makes sense and ends up with someone that know where to start refining the software that should aim for excellence.

I havent taken the time to read the whole post but I appreciate your effort to help enhance the project and help the community

Hello @arhag, we are just trying to set crowdfunding on Steemit to get resources to save Abongphen Highland Forest in Cameroon. Please could you help us in our effort? Thank you verz much for your upvotes and resteems. https://steemit.com/introduceyourself/@kedjom-keku/do-it-for-forest-crowdfunding-who-are-we-actually-forest-friendly-family-each-11usd-10x10-m-forest-saved

Thank you for sharing.very good.

Does he work the same way as steem?

The time-locked savings account feature proposed here would be different than what is currently available in Steem. In Steem you only have a fixed 3 day withdrawal delay option for the savings balances. With this proposal, I wish to provide users with flexibility to essentially allow them to create different buckets with different withdrawal delays, and then decide how much of which assets they wish to place in the different buckets.

The proposal also recommends adopting the recovery account feature of Steem, and in that case it would be the same exact feature.

Yup. More of this

I'm really sorry about my bot voting with too much %. I corrected the vote. @abit's bot should remove his vote soon. Sorry again.

I'll read your post eventually by the way. I think this would be a great addon to BitShares

Fantastic Idea, @arhag! Can we get this implemented?

Good. You have even coded it. Create an issue / pull request on Github please, we'll merge it when got approved.

Not quite ready for a pull request. It still requires quite a bit more work like actually converting a lot of pseudocode and detailed prose description to actual code that works with the BitShares code base, adding database API functions to get pending withdrawals, cli_wallet support, and the most time consuming of all: proper unit tests.

I may do some of the less time-consuming stuff above slowly on my free time. But I would be really thrilled if someone else would like to seriously take on the bulk of the work testing and integrating it (I would imagine a worker proposal for it wouldn't cost too much). Also, to really make it useful to many people we would want to have this supported in the GUI, which is code I won't be working on at all (this should be budgeted as part of the worker as well if someone decides to go that route).

Love this. Excellent proposal and detail.

Don't forget Two-Factor Authentication. We stored our investment to this account, we need extra layer of security in place.

@Yehey
https://QUE.com

What a nice cartoon to illustrate the security risk associated with Bitshares. I bought some Bitshares recently and would definitely like to see this upgrade done to protect my very little and seemly meaningless investment

great stuff

Thank you

thats an amazing idea!

Will be spending the next 2 months attempting to break this post apart...

At least the cartoon is funny!

Hi @arhag, great suggestion. Anything that helps with crypto security can only be a good thing!

good points!! Upvoted!!

Cool article, thank you for posting. Followed and upvoted! :)

Nice post! I voted for you.
Please follow me, I will follow you back! :)

I invite Hon. @dan to visit my poor blog and upvotes if you like some photo, give me some comment to improve my blog, thank you for your support

This is nice and all but will it help me considering I lost access to my Bitshares wallets in 2016? If there is ever going to be a retroactive recovery hardfork please let me know so I can get access to my old wallets.

You are a great person and I don't know what will I say for you seriously you have so much passions

xkcd brought me here! Always enjoy these comics, even if I don't understand all of it!