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_PAYMENTis allowed to contain the asset inpath, so long as it does not apear insendAssetordestAsset, and -
A
MANAGE_OFFERis allowed if it setsamountto 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) == 0are immediately invalid and cannot be filled. This behavior should be close or identical to the status quo. -
-
If
(flags & 3) == COAUTHORIZED_FLAGfor 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_OFFERto reduce theamountof an offer involving the asset (including canceling the offer with andamountof 0), but not to increase the offer. -
The account can receive (but not send) payments and path payments in the asset, up to the
limitof 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
limiton the trustline and the transaction is not cosigned by the issuer, then theCOAUTHORIZED_FLAGis 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;
};