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.
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_object
s and savings_reduce_delay_object
s 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.