bitshares / bsips

BitShares Improvement Proposals and Protocols. These technical documents describe the process of updating and improving the BitShares blockchain and technical ecosystem.
https://bitshares.github.io
63 stars 86 forks source link

BSIP69: Additional Assert Predicates #175

Closed christophersanborn closed 4 years ago

christophersanborn commented 5 years ago
BSIP: 0069
Title: Additional Assert Predicates
Authors: Christopher J. Sanborn, ...
Status: Draft
Type: Protocol
Created: 2019-06-08
Discussion: https://github.com/bitshares/bsips/issues/175

Abstract

New predicates for the assert_operation are proposed to enable conditional validity of transactions for various use cases, including safety of transactions that depend on recent prior transactions, safety of transactions signed by hardware wallets, and transactions signed in advance but intended to be broadcast at a later time. Removal of a stale committee parameter max_predicate_opcode is also proposed, as it appears to be unstewarded, is currently set to a value that prevents utilization of one of the three existing predicates, and is not in line with current conventions for activating new features.

Motivation

This BSIP is motivated by a multitude of potential use cases in which conditional validity of a transaction may be valuable. BitShares currently supports conditional validity of a transaction via the assert operation which will cause a transaction to fail validation if a supplied “predicate” is not met. However, at the present time, the assert operation supports only three distinct predicate types. We propose here additional predicates which will enable new use cases.

One new use case which could be enabled by the new predicates is the implementation of payment channels described in Short-lived Unidirectional Payment Channels, which depends on refund transactions being secured by a time lock. A time lock could be trivially implemented as an assert on a particular head block time.

Another case where new predicates for conditional validity would be useful is for transactions which must be signed without the ability to verify blockchain state at the time of signing. A particular instance of this is transactions signed by a hardware wallet. Many hardware wallets display details of a transaction to the user for confirmation prior to signing. Since BitShares transactions refer to accounts and assets by object ID rather than name or symbol, a hardware wallet cannot present these details to the user in human-readble form unless there is an assurance of a truthful mapping between the displayed account name or asset symbol and the corresponding object IDs in the transaction to be signed. Including an assert operation will ensure that if the mapping is NOT truthful, then the transaction will NOT be valid to include in a block, thus hardware wallet UX can be greatly improved.

In what follows we propose to extend the list of currently available assert predicates with new predicates to serve the above use cases and more.

Rationale

Review of existing predicates

The assert_operation as currently implemented offers only three predicates. They are defined in assert.hpp as:

Predicate Description
account_name_eq_lit_predicate Tests the name of a given account ID equals a particular string literal
asset_symbol_eq_lit_predicate Tests the symbol of a given asset ID equals a particular string literal
block_id_predicate Tests that a block with a given ID appears within the last 2^16 blocks

The existing predicates are primarily geared at ensuring that a newly created account or asset has in fact been created as believed before doing something with that account or asset that could produce unintended consequences were the account or asset not created as believed. This protects against, e.g., a race condition in which a block reorganization could potentially change the numeric account ID given to a newly-registered account name. Since transfer operations identify account IDs, not names, a faucet script providing initial funds to such an account could inadvertently fund the wrong account. The faucet could, of course, wait for irreversibility prior to broadcasting the funding operation, but this adds complexity as the faucet must watch the blockchain. If the faucet prefers to fund the account immediately — perhaps even within the same block as the registration operation — then the assert predicates offer an assurance of safety.

Proposed new predicates:

We propose the addition of the following new predicates, organized by purpose below:

Extending account and asset creation safety:

To further support account and asset creation safety, we propose to add the following predicates:

Predicate Description
account_authorities_match_predicate Tests that account authorities (owner, active, special, etc.) are as supplied in predicate
asset_issuer_is_account_predicate Tests asset issuer

Although the existing predicates already allow to check for the successful creation of a new account or asset by testing the correlation of an object ID and a name string, this still leaves open a vulnerability from an attacking party trying to register the same account name or asset symbol. This would be a targetted attack, in which an attacker would have to observe an attempt to register a particular name and then front-run a competing registration operation that “steals” the name. Although tricky to implement, such an attack is certainly possible. For a registration script wishing to quickly fund a newly-created account, a better way to test successful creation would be to test that the account authorities are as expected, rather than just the correlation of account name to id. Similarly for asset creation, a test of asset issuer offers assurance that the asset created is in fact in control of the expected issuer.

Head block time predicate:

We propose to add the following predicate, which supports applications such as payment channels:

Predicate Description
head_block_time_ge_time_predicate Head block time meets or exceeds time. Ensures a transaction is only valid at or after a specified time.

This predicate allows for the signing of future-dated transactions (up to a practical limit of a little more than two days, imposed by the need of a transaction to reference a TaPoS block within the past 2^16 blocks). Payment channel constructs, such as proposed in BSIP-0063, can use this to sign refund transctions to be used if a counterparty fails to meet an obligation to close out a channel within an agreed upon time window.

Predicates useful to hardware wallets:

The existing predicates account_name_eq_lit_predicate and asset_symbol_eq_lit_predicate, in addition to the use cases described above, are also useful to optimize hardware wallet UX design. We propose one more predicate to complete the hardware-wallet use case:

Predicate Description
asset_precision_predicate Checks precision field of an asset

These predicates are useful to hardware wallets because HW wallets must sign transactions that make reference to object IDs, without being able to verify the human-readable names of those object IDs. Ideally, the wallet would like to present details to the user for confirmation using user-friendly names or symbols. The hardware wallet cannot verify that chain state maps a particular object to a particular string. However, it can protect the user by only signing transactions which are inherently invalid if the expected mapping is not truthful. Thus these assert predicates can allow the wallet to display, e.g., a transfer operation using account name instead of account ID. Similarly for asset symbols. And with the added asset_precision_predicate, the hardware wallet will be able to display asset amounts using the correct precision and symbol without risk of an order-of-magnitude error or incorrect asset type.

Specifications

account_authorities_match_predicate:

/**
 * This predicate takes one or more authority or special_authority objects and
 * asserts that each supplied authority matches the corresponding account
 * authority.  For authorities NOT supplied, no checks are performed.
 * To assert that a particular authority (e.g. owner_special) is EMPTY,
 * do not leave the optional<> field unspecified (which will not perform a
 * check), but rather include it as the `no_special_authority` type variant.
 */
struct account_authorities_match_predicate
{
   account_id_type account_id;

   optional<authority>  owner;
   optional<authority>  active;
   optional<special_authority>  owner_special;
   optional<special_authority>  owner_active;

   // In the event BSIP-40 (Custom Active Authorities) is implemented, add also:
   // optional<vector<custom_authority_object>> custom_authorities;
   // (Typical usage would be to supply zero-length vector to assert the list is
   // empty.)

   /**
    *  Validate that predicate is well-formed.  Verify that at least one optional
    *  parameter is supplied and that they are valid (special_)authority objects.
    */
   bool validate() const;
};

asset_issuer_is_account_predicate:

/**
 * Asserts that asset_id->issuer matches issuer_id.
 */
struct asset_issuer_is_account_predicate
{
   asset_id_type   asset_id;
   account_id_type issuer_id;

   bool validate() const;
};

asset_precision_predicate:

/**
 * Assert that asset_id->precision matches precision.
 */
struct asset_precision_predicate
{
   asset_id_type   asset_id;
   uint8_t         precision;

   bool validate() const;
};

head_block_time_ge_time_predicate:

/**
 * Asserts that the block time of the block which this transaction would
 * build upon (i.e. the parent of the block this tx would be included in)
 * meets or exceeds a specified time.  This allows to create transactions
 * which only become valid “in the future”. (Verifies that
 * database::head_block_time() >= min_time.)
 */
struct head_block_time_ge_time_predicate
{
   fc::time_point_sec min_time;

   bool validate() const;
};

Activation of new predicates and removal of max_predicate_opcode parameter:

A hard-fork date shall be chosen for activation of these new predicates and shall be sufficiently future-dated to allow time for nodes to upgrade. The outdated committee parameter max_predicate_opcode shall be removed, as it is not needed for safe upgrade (see next section for discussion).

Discussion

Removal of max_predicate_opcode parameter:

The set of committee parameters currently includes a parameter called max_predicate_opcode, which in the current protocol must be increased in order to activate new predicates. The parameter is not a necessary protection in the protocol upgrade procedure, as the hard-fork mechanism is already both sufficient and necessary, making this parameter redundant. Nor is there any clear justification for involving the committee in the mechanics of protocol upgrade. In fact, the parameter appears to have already been forgotten, as it was not incremented after the addition of the most recent assert predicate, with the result that block_id_predicate, while defined and supported in the protocol, is not in fact usable on the network. It seems quite unlikely that this was an intentional decision. Thus we think the removal of this parameter and the associated validation checks are justified.

Summary for Shareholders

We have proposed the addition of four new assert predicates which increase the utility and security of the BitShares platform. We have also proposed the removal of a committee parameter max_predicate_opcode as it is not necessary for the safe activation of new predicates.

Copyright

This document is placed by its authors in the public domain.

See Also

pmconrad commented 5 years ago

Thanks!

christophersanborn commented 5 years ago
  • I would remove head_block_time_lt_time_predicate because it is, as you say, redundant with transaction expiration.

Done.

  • I can't really imagine a use-case for the block height predicates right now. I think we shouldn't add predicates for reasons of symmetry or "beauty". :-)

I also can't off the top of my head, think of any use cases that aren't better served by reference to block time. Removed for now.

So my interpretation of that, (but correct me if I'm wrong), is that "block time" is a stamp applied to a block AFTER all transaction have been applied? So that, all transactions in a block can be assumed to have been applied "before" the block time of the block? (But at or after the block time of the previous block.)

In the Specification section I phrase the predicate as asserting that the block time of the parent block is "greater or equal to" the time specified in the predicate. If I'm interpreting correctly, this is what "head block time" actually refers to.

MichelSantos commented 5 years ago

Nice work. This is well motivated, well explained, and well cited.

abitmore commented 4 years ago

Draft merged in #183. Closing.