CAP: 0016
Title: Cosigned assets: NopOp and COAUTHORIZED_FLAG
Authors: David Mazières
Status: Rejected (In favor of CAP-0018)
Created: 2018-01-24
Discussion: https://github.com/stellar/stellar-protocol/issues/146
Protocol version: TBD
A number of Stellar asset issuers need greater control over assets than simply authorizing particular accounts to hold the asset. As a result, we are seeing people build solutions in which the asset issuer is actually a co-signer on asset holders' accounts. This solution has several drawbacks. In particular, the asset owner cannot unilaterally withdraw XLM or other assets from his or her account, and it is very hard to trade one such restricted asset for another (as both issuers would need to be cosigners on the account, giving one issuer permission to authorize transactions on the other's asset).
This proposal is a first cut at attempting to address the problem through minimal stellar-core changes.
To accommodate asset issuers with restrictions, we propose adding
another mode for trustlines of AUTH_REQUIRED
assets, called
"cosigner authorized," that lies somewhere between being not
authorized and fully authorized. The idea is to allow issuers to
require asset holders to request co-signing of transactions involving
the asset without being cosigners on asset holders' accounts.
enum TrustLineFlags
{
// issuer has authorized account to perform transactions with its credit
AUTHORIZED_FLAG = 1,
// issuer allows transactions that are co-signed by issuer low threshold
COAUTHORIZED_FLAG = 2
};
There are now three modes for the trustline of an AUTH_REQUIRED
asset, based on TrustLineEntry::flags
:
-
If
(flags & 3) == 0
, the source account is not authorized. AnyPAYMENT
,PATH_PAYMENT
,MANAGE_OFFER
, orCREATE_PASSIVE_OFFER
, with the source account causes the whole transaction to fail, with only two exceptions:-
A
PATH_PAYMENT
is allowed to contain the asset inpath
, so long as it does not apear insendAsset
ordestAsset
, and -
A
MANAGE_OFFER
is allowed if it setsamount
to 0 (to delete the offer).
In addition, any operation with a different (even authorized) source account will fail if it attempts to send the asset to an account with
(flags & 3) == 0
. Finally, existing orders on the order book belonging to the account with(flags & 3) == 0
are immediately invalid and cannot be filled. This behavior should be close or identical to the status quo. -
-
If
(flags & 3) == COAUTHORIZED_FLAG
for an account, then the account has certain additional permissions compared to when those bits are 0. Specifically:-
The account's offers in the order book continue to be valid, and can be filled at any time.
-
The account can use
MANAGE_OFFER
to reduce theamount
of an offer involving the asset (including canceling the offer with andamount
of 0), but not to increase the offer. -
The account can receive (but not send) payments and path payments in the asset, up to the
limit
of the trustline.
However, any other operation on the asset requires that the transaction be co-signed by the asset issuer at low threshold. Moreover, if the account increases the
limit
on the trustline and the transaction is not cosigned by the issuer, then theCOAUTHORIZED_FLAG
is cleared so(flags & 3)
goes back to 0. -
-
If
(flags & 3) == AUTHORIZED_FLAG
, then the account can perform arbitrary transactions on the asset, including sending payments to accounts withCOAUTHORIZED_FLAG
.
Implementing cosigned assets requires changes to two operations.
Because an asset issuer's signature may be out of place on a
transaction requiring asset issuer coauthorization, we add a NopOp
operation that requires a signature from an arbitrary source account.
Such an operation will be useful in other contexts, too, as currently
pre-authorized transactions sometimes require ugly hacks like having
an account send one Stroup to itself. Second, we have to modify
ALLOW_TRUST
to enable issuers to set the new COAUTHORIZED_FLAG
.
The NO_OPERATION
operation does nothing, but requires a valid
signature from the operation's source account. Since the default
source account is the source of the transaction, this will always
succeed at low threshold if the operation's sourceAccount
is
NULL
. However, it can be used to require a low threshold signature
from an asset issuer.
enum NopOp {
LOW_THRESHOLD = 0,
MED_THRESHOLD = 1,
HIGH_THRESHOLD = 2
};
enum NopResult {
NOP_SUCCESS = 0,
NOP_BAD_THRESHOLD = -1, // the operation was an invalid value
};
enum OperationType
{
...
BUMP_SEQUENCE = 11,
NO_OPERATION = 12
};
struct Operation
{
union switch (OperationType type)
{
case NO_OPERATION:
NopOp nopOp;
}
body;
};
union OperationResult switch (OperationResultCode code)
{
case opINNER:
union switch (OperationType type)
{
...
case NO_OPERATION:
NopResult nopResult;
}
tr;
default:
void;
};
NopOp
is not strictly necessary, as a redundant ALLOW_TRUST
can be
used instead, but having NopOp
is cleaner and has other applications
to pre-authorized transactions.
Only one small change is required to AllowTrustOp
. We change the
authorize
field from a bool
to a uint32
. This provides binary
compatibility with clients that do not know about the
COAUTHORIZED_FLAG
(since AUTHORIZED_FLAG == TRUE
in XDR), but
having a uint32
provides the additional option of setting it to
COAUTHORIZED_FLAG
.
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, AUTHORIZED_FLAG, or COAUTHORIZED_FLAG
uint32 authorize;
};