Skip to content

Latest commit

 

History

History
133 lines (96 loc) · 9.97 KB

cap-0018.md

File metadata and controls

133 lines (96 loc) · 9.97 KB

Preamble

CAP: 0018
Title: Fine-Grained Control of Authorization
Author: Jonathan Jove
Status: Final
Created: 2019-01-31
Discussion: https://github.com/stellar/stellar-protocol/issues/146
Protocol version: 13

Simple Summary

This proposal provides issuers with a level of authorization between unauthorized and fully authorized. This level of authorization will allow a trustline to maintain liabilities (see CAP-0003) without permitting any other operations.

Abstract

This proposal adds a new flag to TrustLineFlags which offers a level of authorization intermediate between unauthorized and fully authorized. It is then shown how this new flag enables similar behavior to CAP-0016 by carefully setting and toggling the level of authorization. Additional flags could also be added, which would enable fine-grained control of authorization on a per-account basis.

Motivation

A number of Stellar asset issuers need greater control over assets than simply authorizing particular accounts to hold the asset. The TrustLineEntry.flags field allows an issuer to toggle between the following two states:

  • TrustLineEntry.flags == 0: The account can hold a balance but cannot receive payments, send payments, maintain offers, or manage offers
  • TrustLineEntry.flags == AUTHORIZED_FLAG: The account can hold a balance, receive payments, send payments, maintain offers, and manage offers

If the issuer does not intend to allow the account to maintain offers, then toggling between the two states described above can be used to control what accounts do with their assets. To achieve this, an issuer could leave accounts in the unauthorized state. In order to do anything with the assets an account holds, that account would need to become authorized. This would require the signature of the issuer. The owner of such an account could then request that the issuer sign a transaction; the issuer should refuse to sign any transaction that does not return the account to the unauthorized state. A transaction that an issuer might be willing to sign could look like

  • Operation 1: Issuer uses AllowTrust to fully authorize account A, asset X
  • Operation 2: Issuer uses AllowTrust to fully authorize account B, asset X
  • Operation 3: Payment from A to B
  • Operation 4: Issuer uses AllowTrust to unauthorize account B, asset X
  • Operation 5: Issuer uses AllowTrust to unauthorize account A, asset X

which would require the signatures of the issuer and account A.

But this approach does not work if the account should be allowed to maintain offers. As soon as the account becomes unauthorized, all of its offers will be removed from the ledger. So some issuers have taken a more drastic approach, where the issuer is actually a signer on the asset holders' accounts. This solution has several drawbacks:

  1. The owner of an account cannot unilaterally utilize assets unrelated to the issuer without the signature of that issuer
  2. If multiple issuers want to become signers on a single account, then many signatures are potentially required for simple operations
  3. Issuers cannot easily rotate keys, because their keys are on every account holding their asset

Specification

This proposal requires the addition of AUTHORIZED_TO_MAINTAIN_LIABILITIES_FLAG to TrustLineFlags, so the XDR becomes

enum TrustLineFlags
{
    AUTHORIZED_FLAG = 1,                        // issuer has authorized account to perform transactions with its credit
    AUTHORIZED_TO_MAINTAIN_LIABILITIES_FLAG = 2 // issuer has authorized account to maintain and reduce liabilities for its credit
};

// mask for all trustline flags
const MASK_TRUSTLINE_FLAGS = 3;

In order to interact with these flags, the type of the authorize field of AllowTrustOp must be changed from bool to uint32. This provides binary compatibility with clients that do not know about the new flags (since AUTHORIZED_FLAG == TRUE in XDR), but having a uint32 provides the additional option of setting it to any combination of TrustLineFlags. The XDR becomes

struct AllowTrustOp
{
    AccountID trustor;
    union switch (AssetType type)
    {
    // ASSET_TYPE_NATIVE is not allowed
    case ASSET_TYPE_CREDIT_ALPHANUM4:
        opaque assetCode4[4];

    case ASSET_TYPE_CREDIT_ALPHANUM12:
        opaque assetCode12[12];

        // add other asset types here in the future
    }
    asset;

    // 0, or any bitwise combination of TrustLineFlags
    uint32 authorize;
};

The new behavior of ALLOW_TRUST is to set flags = authorize on the relevant trustline. Semantically, this is equivalent to the existing behavior of ALLOW_TRUST because

  • authorize == FALSE becomes authorize == 0 which removes any authorization from the trustline
  • authorize == TRUE becomes authorize == AUTHORIZED_FLAG which makes the trustline fully authorized

The combination AUTHORIZED_FLAG | AUTHORIZED_TO_MAINTAIN_LIABILITIES_FLAG is not valid because AUTHORIZED_FLAG implies AUTHORIZED_TO_MAINTAIN_LIABILITIES_FLAG. Trying to set authorize to any value above AUTHORIZED_TO_MAINTAIN_LIABILITIES_FLAG is also invalid. ALLOW_TRUST_MALFORMED is the error code that will be returned.

If AUTH_REVOCABLE_FLAG is not set, the transition from AUTHORIZED_FLAG to AUTHORIZED_TO_MAINTAIN_LIABILITIES_FLAG is invalid. ALLOW_TRUST_CANT_REVOKE is the error code that will be returned.

If a trustline tl has tl.flags == AUTHORIZED_TO_MAINTAIN_LIABILITIES_FLAG then

  1. tl.balance > 0 is permitted (this is always true for any value of tl.flags)
  2. tl.liabilities.buying > 0 is permitted (this is not true if tl.flags == 0)
  3. tl.liabilities.selling > 0 is permitted (this is not true if tl.flags == 0)
  4. tl.balance can only change if an existing liability is converted into a balance. (this is not true if tl.flags == AUTHORIZED)
  5. No operation which creates or modifies an offer buying or selling tl.asset is permitted but deleting such offers is permitted. (this is not true if tl.flags == AUTHORIZED)
  6. Upgrades should treat AUTHORIZED_TO_MAINTAIN_LIABILITIES_FLAG the same way as AUTHORIZED_FLAG because liabilities can only be decreased on upgrades (since protocol version 11). An invariant for this should be added.

Rationale

How can this new flag be used?

Using AUTHORIZED_TO_MAINTAIN_LIABILITIES_FLAG enables issuers to control what accounts do with their assets even if the accounts should be allowed to maintain offers. To achieve this, an issuer could leave accounts in the AUTHORIZED_TO_MAINTAIN_LIABILITIES_FLAG state. Such an account is allowed to maintain liabilities, meaning it can own offers but cannot otherwise do anything with the asset. In order to do anything with the assets an account holds, that account would need to become authorized. This would require the signature of the issuer. The owner of such an account could then request that the issuer sign a transaction; the issuer should refuse to sign any transaction that does not return the account to the AUTHORIZED_TO_MAINTAIN_LIABILITIES_FLAG state. A transaction that an issuer might be willing to sign could look like

  • Operation 1: Issuer uses AllowTrust to fully authorize account A, asset X
  • Operation 2: Issuer uses AllowTrust to fully authorize account B, asset X
  • Operation 3: Payment from A to B
  • Operation 4: Issuer uses AllowTrust to set account B, asset X to AUTHORIZED_TO_MAINTAIN_LIABILITIES_FLAG state
  • Operation 5: Issuer uses AllowTrust to set account A, asset X to AUTHORIZED_TO_MAINTAIN_LIABILITIES_FLAG state

or

  • Operation 1: Issuer uses AllowTrust to fully authorize account A, asset X
  • Operation 2: Account A manages offer to buy or sell X
  • Operation 3: Issuer uses AllowTrust to set account A, asset X to AUTHORIZED_TO_MAINTAIN_LIABILITIES_FLAG state

both of which would require the signatures of the issuer and account A.

In comparison to CAP-0016 this proposal would prohibit a fully authorized account from sending to an account in the AUTHORIZED_TO_MAINTAIN_LIABILITIES_FLAG state without the signature of the issuer, which could be a desirable feature because it means issuers entirely control their asset. But this solution is flexible in that it would be possible to add other flags to TrustLineFlags which would control other aspects of authorization. For example, one could add the flag AUTHORIZED_TO_INCREASE_BALANCE_FLAG which would permit an account to receive payments. The behavior of CAP-0016 could be emulated entirely with this additional flag. An appropriate set of such flags would provide issuers with fine-grained control of authorization on a per-account basis, which justifies the name of this proposal.

Why requirement (5)?

Note that requirement (5) implies but is not equivalent to

  1. No operation which increases tl.liabilities.buying is permitted
  2. No operation which increases tl.liabilities.selling is permitted

Although requirements (6) and (7) seem more natural than requirement (5), since they refer only to individual values in the ledger like the other requirements, they are actually insufficient. If requirements (6) and (7) were included instead of (5) then it would be possible for account A to modify an offer selling asset X for asset Y and change it into an offer selling asset X for asset Z without a signature from the issuers of X or Y even if A is only AUTHORIZED_TO_MAINTAIN_LIABILITIES_FLAG for one or both of those assets. This would allow accounts to easily evade restrictions from the issuer on which assets can be exchanged for X. Requirements (6) and (7) would also make it possible for accounts to change the price of offers without a signature from the relevant issuer or issuers.

Backwards Compatibility

The only backwards incompatibility with this proposal is if downstream systems relied on the fact that !(TrustLineEntry.flags & AUTHORIZED_FLAG) implies unauthorized. In this case, downstream systems might erroneously conclude that an account is unauthorized when it is in fact in the AUTHORIZED_TO_MAINTAIN_LIABILITIES_FLAG state.

Test Cases

None yet.

Implementation

stellar/stellar-core#2387