CAP: 0007
Title: Deterministic Account Creation
Author: Jeremy Rubin
Status: Draft
Created: 2018-11-07
Discussion: https://github.com/stellar/stellar-protocol/issues/99
Protocol version: TBD
We introduce CreateDeterministicAccount operation which creates an account that could only be created by the transaction creating it, allowing it to have a known starting sequence number and initialization.
Deterministic accounts are a critical infrastructure piece for building reliable contracts for three key reasons:
- Predictable sequence numbers
- Never-fail creation
- Known initialized state
We create deterministic accounts with a few changes to account structures to support non-key account IDs.
Currently, when a contract must create an account as a part of it's settlement (such as in Starlight or in an Account Transfer), creating the account results in an unpredictable sequence number which prevents the presigning of transactions for it. Furthermore, accounts can not be created and modified at the same time preventing initializing an account on behalf of another party.
We extend the AccountID type:
enum AccountIDType
{
ACCOUNT_ID_TYPE_ED25519 = KEY_TYPE_ED25519,
ACCOUNT_ID_TYPE_HASH_DET_ACCOUNT = 3,
ACCOUNT_ID_TYPE_NEWEST_DET_ACCOUNT = 4,
ACCOUNT_ID_TYPE_REF_NEW_DET_ACCOUNT = 5,
};
union AccountID switch (AccountIDType type)
{
case ACCOUNT_ID_TYPE_ED25519:
uint256 ed25519;
case ACCOUNT_ID_TYPE_HASH_DET_ACCOUNT:
uint256 ed25519;
case ACCOUNT_ID_TYPE_NEWEST_DET_ACCOUNT:
void;
case ACCOUNT_ID_TYPE_REF_NEW_DET_ACCOUNT:
int64 which;
};
We add the following operation:
struct CreateDeterministicAccountOp
{
int64 startingBalance;
};
The result of this operation is an account created with the following state:
AccountEntry
{
accountID = SHA512().hash(tx.hash)
.hash(op.index)
.digest(),
balance = op.startingBalance,
seqNum = 0,
masterWeight = 0,
}
Following this operation, inside the scope of the transaction executing, the
transactions implicit account ID is ACCOUNT_ID_TYPE_NEWEST_DET_ACCOUNT
. This
allows a transaction to refer to the deterministic account before hashing.
Inside the scope of the transaction creating it, the deterministic account is also implicitly unlocked with the highest threshold.
Therefore subsequent operations with source account set to
ACCOUNT_ID_TYPE_NEWEST_DET_ACCOUNT
such as SetOptions
modify this account.
This operation works with Account Merge, but subsequently
ACCOUNT_ID_TYPE_NEWEST_DET_ACCOUNT
refers to no account.
Alternatively, we may store a vector of pointers to the accounts being created
(in the implementation) and refer to them by creation order. In this case, we
use ACCOUNT_ID_TYPE_REF_NEW_DET_ACCOUNT
. which=0
points to the first
account created, 1
the second, etc. which= = -1
refers to the most recently
created account, -2
the next most recently created, etc. Only one of
ACCOUNT_ID_TYPE_REF_NEW_DET_ACCOUNT
or ACCOUNT_ID_TYPE_NEWEST_DET_ACCOUNT
should make it into the final draft of the spec.
The account has no master signer, therefore setting masterWeight has no effect (but can be used as an extra data field if desired).
Deterministic accounts may always be merged even if the sequence number is above the current ledger, as there is no way to recreate them after merge.
CreateDeterministicAccount is a minimal deterministic account derivation scheme.
The newly created account is deterministically derived from
- The transaction creating it, which transitively commits to:
- The Source Account
- The Source Account Sequence
- The index of the operation in the transaction (making a unique key if repeated CreateDeterministicAccount operations)
Alternative schemes to this deterministic account proposal derive only on the source account, sequence number, and operation index rather than the complete transaction hash. While strictly speaking, this should work for many of the use cases where deterministic accounts are desired, and does not encumber the complexity of not knowing the account ID inside the transaction, there are two main drawbacks that leade to this approach being superior. The first drawback is that there are multiple possible transactions that could create the account, which makes it possible for a malicious actor to trick another agent in certain cases. For example, if a 2 party protocol is proposed by the malicious actor, signatures are acquired, but then the malicious actor aborts, and then the protocol is restarted with different values, the other party may not recall that they have already approved a transaction in the first protocol. The second drawback is that because of the multiplicity of generation of this variant of deterministic account, the existence of the account does not serve as a testament to the success of the operations in the transaction which created the account. Such testaments are useful for complex contracts as they can rely on state implied by the existence of an account (such as the current signer of another account modified in the generating transaction being the same signer as a third account). The abscence of these two drawbacks simultaneously makes this version of deterministic accounts less useful for attackers and more useful for contracting.
An example of using CreateDeterministicAccount is as follows:
Tx0:
Operations:
CreateDeterministicAccount 10 XLM
SetOptions PUBLIC_KEY_TYPE_NEWEST_DET_ACCOUNT
- low threshold: 1
- med threshold: 1
- hi threshold: 1
- signer: {ALICE'S KEY, 1}
ChangeTrust PUBLIC_KEY_TYPE_NEWEST_DET_ACCOUNT
- Line: Some Line
- Limit: None
CreateDeterministicAccount 10 XLM
SetOptions PUBLIC_KEY_TYPE_NEWEST_DET_ACCOUNT
- low threshold: 1
- med threshold: 1
- hi threshold: 1
- signer: {BOB'S KEY, 1}
ChangeTrust PUBLIC_KEY_TYPE_NEWEST_DET_ACCOUNT
- Line: Some Line
- Limit: None
The in-transaction setup guarantees that if the account is created it is in the desired state.
Protocols around using CreateDeterministicAccount will be the subject of a subsequent SEP.
Because of the hash cycle (account ID is transaction hash, transaction therefore can't include it), pre-auth transactions will not work immediately for Deterministic Accounts as described. A subsequent CAP will propose allowing a preauth transaction to specify self as the source account for a pre-auth, which relaxes this constraint.
The newest account account ID type is of limited use. There are certain cases that it does not work for (such as making two deterministic accounts which approve trust lines for each other). The alternative form adds more complexity and bookkeeping cost, but provides a simpler API for implementers and supports all possible initializations. If the added complexity is not too much, it should be preferred. The ability to refer by negative indexes is useful for defining templates which should nest inside of other transactions without need to recompute indexes.
The decision to not include the master signer is a trade off. On the one hand, it means deterministic accounts cannot include as many public key signers as non-deterministic accounts. On the other hand 20 signers is likely enough for most use cases, and the lack of the master key makes account handling code simpler. MuSig based signature constructions also support constructing larger multisig circuits as well, so it is unclear how much utility is gained by having one more signer. Another field for a signer could be added to deterministic accounts, but this would bloat the account struct further and would break compatibility with older software. Potentially, the 20 signer limit could be changed to 21 and run time checked (somehow) to only be allowed for deterministic accounts. This would allow SDK authors to provide identical interfaces. If this is needed, it should be the subject of a separate CAP finalized in conjunction with this CAP. The other ergonomics issue is that it might be surprising to a implementer that there is no default signer and might result in them creating accounts in a frozen state. This is desirable however -- for many types of smart contract, there is no such concept as a master signer (just a few determinisitc steps) and for issuing fixed supply or fixed schedule tokens you would only want to use pre signed transactions and a master signer would be superflous.
This proposal requires updating existing APIs to accept the new key types.
Future work should clean up the XDR AccountID typedef to be an enum.
No implementation yet.