Description
(Posting on behalf of David Mazières)
Motivation
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.
Co-signer authorized trust lines
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. Any
PAYMENT
,PATH_PAYMENT
,MANAGE_OFFER
, or
CREATE_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 thelimit
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 thelimit
on the trustline and
the transaction is not cosigned by the issuer, then the
COAUTHORIZED_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
.
Operation changes
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
.
NO_OPERATION
(NopOp
)
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.
ALLOW_TRUST
flags
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;
};