SEP: 0030
Title: Account Recovery: multi-party recovery of Stellar accounts
Author: Leigh McCulloch <@leighmcculloch>, Lindsay Lin <@capybaraz>
Track: Standard
Status: Draft
Discussion: https://groups.google.com/forum/#!topic/stellar-dev/SFr2dHBZlsY
Created: 2019-12-20
Updated: 2021-05-04
Version: 0.8.1
This protocol defines an API that enables an individual (e.g., a user or wallet) to regain access to a Stellar account that it owns after the individual has lost its private key without providing any third party control of the account. Using this protocol, the user or wallet will preregister the account and a phone number, email, or other form of authentication with one or more servers implementing the protocol and add those servers as signers of the account. If two or more servers are used with appropriate signer configuration no individual server will have control of the account, but collectively, they may help the individual recover access to the account. The protocol also enables individuals to pass control of a Stellar account to another individual.
Implementers and clients of this protocol must also implement SEP-10.
The ability for users to gain access to a Stellar account using credentials other than the account keys is a critical feature of wallets that support the following use cases for a user:
- Recover – Recover access to Stellar accounts for which they may have lost keys.
- Share – Gain access to Stellar accounts that another user intends to share with them.
Solutions exist for wallets to store keys for users and to allow users to retrieve those keys, such as the keystore service, but those solutions usually prevent the server from controlling the account by requiring the user to remember an encryption password that, if forgotten, means permanent loss of the account. Those solutions also don't typically support sharing accounts.
The user experience is improved if gaining access to a Stellar account does not require remembering a password.
This protocol defines an API allowing a client to register an account with a server, and later ask the server to sign transactions for the account.
The registration flow is as follows:
- The client uses SEP-10 to get a JWT proving high threshold control of an account.
- The client, authenticated as the account, registers with the server, providing:
- One or more identities of individuals that can request the server sign transactions for the account, and for each identity:
- The identities role in the client's use case.
- The authentication methods that can be used to authenticate as this identity:
- One or more other Stellar addresses.
- And/or, one or more phone numbers.
- And/or, one or more email addresses.
- And/or, one or more other external authentication methods.
- One or more identities of individuals that can request the server sign transactions for the account, and for each identity:
- The server verifies the JWT is valid according to SEP-10.
- The server verifies that the account the JWT proves control of is the account being registered.
- The server verifies that the account is not already registered.
- The server stores the account address and alternative identities.
- The server generates a unique random signing key for the account.
- The server encrypts the signing secret key and stores it.
- The server responds to the client with the signing public key.
- The client adds the signing public key as a signer of the account with an appropriate weight.
A client can register with multiple servers and give each signing key a weight that is appropriate for the use case the client and servers are targeting. For example, if a client intends to use two servers that should individually have no control of the account but together are able to approve transactions, then the client could configure account thresholds to high 2, med 2, low 2, and assign weights 1 to both server signing keys.
A client can register with a single server and give the servers signing key a weight that is appropriate for the use case the client and server are targeting. For example, if a client intends to use a single server and give that server control of the account, then the client could configure account thresholds to high 1, med 1, low 1, and assign weight 1 to the server signing key.
The signing flow is as follows:
- The client uses the servers authentication provider to get a JWT proving possession of the phone number, email address, or other identity.
- Alternatively, the client uses SEP-10 to get a JWT proving high threshold control of another account that is listed as an alternative identity of the account to be accessed.
- The client, authenticated as the external authentication method (e.g. phone, email, etc) or another account, requests the server to sign a transaction.
- The server verifies that the JWT is valid according to the authentication provider's specification.
- The server verifies that the external authentication method (e.g. phone, email, etc) or account in the JWT is an alternative identity of the account.
- The server verifies that the source account of the transaction is one of:
- The account the client has authenticated for.
- In some unique cases an account the server knows definitively for the validity period of the transaction will not be authorized by the signing key.1
- The server verifies that the source account of each operation in the transaction are one of:
- Not set.
- The account the client has authenticated for.
- In some unique cases an account the server knows definitively for the validity period of the transaction will not be authorized by the signing key.1
- The server signs the transaction with the signing secret key.
- The server responds to the client with the signature.
The client repeats the signing flow with each server it needs to meet the accounts threshold to be able to successfully submit a transaction.
The client can use this process to sign a transaction with the Set Options operation to add a new signer to the account to regain control of an account of which they have lost the key, or to gain control of an account shared with them.
A client can also use the endpoints in the specification to:
- Discover registered accounts that it has access to.
- Update the identities associated with an account.
- Find out if a new signing key is available due to signing key rotation.
- Delete accounts.
Notes:
- See Endpoints
POST /accounts/<address>/sign/<signing-address>
for more details.
This protocol is intended to satisfy a wide range of use cases, but initially the two use cases mentioned under Motivation are apparent.
An individual that expects to maybe lose access to their keys can use SEP-30 to recover access to their Stellar accounts.
A single identity is sufficient and specifying a role would be unnecessary. The individual registers the account with two or more servers and gives each server signer weights so that no individual server can control the account, but together they can approve transactions.
The individual can recover the account by authenticating with all servers and requesting all servers sign a transaction giving a new key signing weight sufficient to meet the high threshold of the account.
An individual that wishes to share an account with another user and allow that other user to gain access to the account by recovering it from two or more servers.
Multiple identities should be specified each with a unique role so that when
each individual is authenticated using an identities authentication methods
they can recognize their role in relation to the account. In this case using
roles such as sender
and receiver
are appropriate.
Either individual can recover the account by authenticating with all servers and asking all servers to sign a transaction giving a new key signing weight sufficient to meet the high threshold of the account.
The receiver can accept the account and either take ownership of it or merge it into another account by authenticating with all servers and requesting all servers sign a transaction giving a new key signing weight sufficient to meet the high threshold of the account. The receiver can then merge the account into their primary account. If the receiver chooses to keep the account they can delete the account from the servers or replace the identities so that the sender is no longer an identity authorized to recover the account.
The sender may wish to recover the account if the receiver does not and can use the same process.
The sender and receiver can recognize their relationship to an account by inspecting the authenticated identities role.
All endpoints require authentication in the form of a JWT token that is passed
in the Authorization
header. The JWT can be issued by two types of issuers.
Some endpoints require a specific provider while others should function with
either. The two issuers are:
SEP-10
: A SEP-10 Web Auth server for proving high threshold control of an account. The SEP-10 server should be configured to only issue JWTs if signers prove they meet the high threshold on the account.External
: Defined by the implementer. Proves possession of an identity that is external to the Stellar network and can be any electronic or physical identity. Examples of authentication identities that could be used are authentication of a phone number, email addres, or with an OpenID Connect provider, or verifying a physical address, national identity document, passport. Any forms of identity may be supported including those not formally specified in this protocol.
All endpoints accept in requests the following Content-Type
:
application/json
All endpoints respond with content type:
application/json
If an error occurs when calling any endpoint, an appropriate HTTP status code will be returned along with an error response.
Common HTTP status codes may be returned for a server. In particular the following are expected:
Status Code | Name | Reason |
---|---|---|
400 |
Bad Request | The request is invalid in anyway. |
401 |
Unauthorized | No Authorization header has been provided or the contents of the header are not accepted as valid. |
404 |
Not Found | The path is not recognized by the server, or the address does not have an account, or the authenticated user does not have permission to access the account. |
409 |
Conflict | The account is already registered, when attempting to register an account that is already registered. |
Name | Type | Description |
---|---|---|
error |
string | A description of the error. |
{
"error": "..."
}
A set of common fields are used to describe an account in requests and responses on most endpoints.
Name | Type | Description |
---|---|---|
identities |
array | A list of identities that if any one is authenticated will be able to gain control of this account. |
identities[].role |
string | The role of the identity. This value is not used by the server and is stored and echoed back in responses as a way for a client to know conceptually who each identity represents. |
identities[].auth_methods |
array | A list of authentication methods that can be used to authenticate as this identity. |
identities[].auth_methods[].type |
string | The type of identity. See Common Authentication Methods. |
identities[].auth_methods[].value |
string | The identifier or value that uniquely identifies the identity that if authenticated will be given full permissions of the account. For example if the type of authentication is an email address, the value is the email address. |
Implementers define the external authentication methods supported by the implementation. If an implementation supports any of the following common authentication methods the type values below should be used along with the specifications for how to format the identifiers to improve interopability between clients and servers that may have multiple integraters.
Type | Description |
---|---|
stellar_address |
A Stellar address in the strkey form G... , proven via SEP-10. |
phone_number |
A phone number. When encoding a phone number the ITU-T recommendations E.123 and E.164 should be followed. The phone number should be formatted in international notation with a leading + as demonstrated in E.123 Section 2.5 to ensure phone numbers are consistently encoded and are globally unique. Spaces should be omitted. |
email |
An email address. |
{
"identities": [
{
"role": "owner",
"auth_methods": [
{ "type": "stellar_address", "value": "GDUA..." },
{ "type": "phone_number", "value": "+10000000001" },
{ "type": "email", "value": "[email protected]" }
]
}
]
}
{
"identities": [
{
"role": "sender",
"auth_methods": [
{ "type": "stellar_address", "value": "GDUA..." },
{ "type": "phone_number", "value": "+10000000001" },
{ "type": "email", "value": "[email protected]" }
]
},
{
"role": "receiver",
"auth_methods": [
{ "type": "stellar_address", "value": "GBEA..." },
{ "type": "phone_number", "value": "+10000000002" },
{ "type": "email", "value": "[email protected]" }
]
},
]
}
Name | Type | Description |
---|---|---|
address |
string | The Stellar account ID or address of the account. |
identities |
array | A list of identities that if any one is authenticated will be able to gain control of this account. |
identities[].role |
string | Same as request. See above. |
identities[].authenticated |
boolean | Optional. Present and true if the currently authenticated client is authenticated with the identity. |
signers |
array | The signers' public keys that the server can sign transactions with for this account when requested by an authenticated user, ordered from most recently added to least recently added. |
signers[].key |
string | The signer public key that the server can sign transactions with for this account when requested by an authenticated user. |
{
"address": "GFDD...",
"identities": [
{ "role": "owner", "authenticated": true }
],
"signers": [
{ "key": "GADF..." }
]
}
{
"address": "GFDD...",
"identities": [
{ "role": "sender", "authenticated": true },
{ "role": "receiver" }
],
"signers": [
{ "key": "GADF..." }
]
}
POST /accounts/<address>
PUT /accounts/<address>
POST /accounts/<address>/sign/<signing-address>
GET /accounts/<address>
DELETE /accounts/<address>
GET /accounts
This endpoint registers an account.
The owner identities are the alternative identities of the user who owns the account.
The other identities are the identities of any other person who the owner is declaring should be allowed to gain control of the account.
See Common Fields.
{
"identities": [
{
"role": "sender",
"auth_methods": [
{ "type": "stellar_address", "value": "GDUA..." },
{ "type": "phone_number", "value": "+10000000001" },
{ "type": "email", "value": "[email protected]" }
]
},
{
"role": "receiver",
"auth_methods": [
{ "type": "stellar_address", "value": "GBEA..." },
{ "type": "phone_number", "value": "+10000000002" },
{ "type": "email", "value": "[email protected]" }
]
},
]
}
See Common Fields.
{
"address": "GFDD...",
"identities": [
{ "role": "owner" }
],
"signers": [
{ "key": "GADF..." }
]
}
{
"address": "GFDD...",
"identities": [
{ "role": "sender" },
{ "role": "receiver" }
],
"signers": [
{ "key": "GADF..." }
]
}
This endpoint updates the identities for the account. The identities should be entirely replaced with the identities provided in the request, and not merged. Either owner or other or both should be set. If one is currently set and the request does not include it, it is removed.
See Common Fields.
{
"identities": [
{
"role": "sender",
"auth_methods": [
{ "type": "stellar_address", "value": "GDUA..." },
{ "type": "phone_number", "value": "+10000000001" },
{ "type": "email", "value": "[email protected]" }
]
},
{
"role": "receiver",
"auth_methods": [
{ "type": "stellar_address", "value": "GBEA..." },
{ "type": "phone_number", "value": "+10000000002" },
{ "type": "email", "value": "[email protected]" }
]
},
]
}
See Common Fields.
{
"address": "GFDD...",
"identities": [
{ "role": "sender" },
{ "role": "receiver" },
],
"signers": [
{ "key": "GADF..." }
]
}
This endpoint signs a transaction that has operations for the account using the signing key associated with the signing address specified in the path.
The signing address specified must be one of the signing keys that the server has provided as a signer. The client should use the signing address that has been added as a signer to the account. The server may generate new signing keys over time as a best practice in rotating keys. All keys generated will be available for signing. An account should check what signers are available periodically and update the signer on the account to be the most recently generated signer.
A server must only authorize operations in signed transactions that operate on the registered account that the client has authenticated for. This is to ensure that the server does not authorize operations for an account the client is not authorized. There are multiples ways to achieve this depending on the use cases of the server.
The simplest approach is to require the transaction to only contain operations for the authenticated account. The server can verify that the source account of the transaction and the source account of all operations in the transaction match the account in the request.
In some cases a server may need to sign transactions that contain other accounts. For example, a sponsoring account using operations defined in CAP-33. A server could be implemented to allow transactions to contain that specific sponsoring account as a source account. The server operator must ensure that the signing keys used by the server are never signers of the sponsoring account to ensure an authenticated client cannot sign for the sponsoring account.
The transaction can contain any operations, but it is anticipated for the use cases discussed earlier in this protocol that the transaction will contain an operation to add a new signer to the account and possibly to remove an old signer or to merge an account. The signature will be generated using the signing key that the server generated during registration for the account.
Name | Type | Description |
---|---|---|
transaction |
string | A XDR base64 encoded Stellar transaction. |
{
"transaction": "AAAAAHAHhQtYBh5F2zA6...",
}
Name | Type | Description |
---|---|---|
signature |
string | The base64 encoded signature that is the result of the server signing a transaction. |
network_passphrase |
string | The network passphrase used when generating a signature. |
{
"signature": "YpVelqPAKsYTP...",
"network_passphrase": "Test SDF Network ; September 2015"
}
This endpoint returns the registered account’s details.
In this response and in other responses the identities are not included in the response to ensure that for shared accounts one person’s identities are not leaked to another.
See Common Fields.
{
"address": "GFDD...",
"identities": [
{ "role": "sender", "authenticated": true },
{ "role": "receiver" },
],
"signers": [
{ "key": "GADF..." }
]
}
This endpoint will delete the record for an account. This should be irrecoverable.
See Common Fields.
{
"address": "GFDD...",
"identities": [
{ "role": "sender", "authenticated": true },
{ "role": "receiver" },
],
"signer": "GADF...",
"signers": [
{ "key": "GADF..." }
]
}
This endpoint will return a list of accounts that the JWT allows access to. This includes the account authenticated with a SEP-10 JWT, or any account that has the account authenticated as an alternative identity, or any account that has the phone number or email address or any other supported identity with a JWT from another authentication provider.
Name | Type | Description |
---|---|---|
after |
string | Used for cursor-based pagination to get results after the given cursor. Use the value of the address of the last account in the current page to get the next page. |
Name | Type | Description |
---|---|---|
accounts |
array | A list of accounts accessible by the authenticated client. |
accounts[].* |
object | See Common Fields. |
{
"accounts": [
{
"address": "GEAC...",
"identities": [
{ "authenticated": true }
],
"signers": [
{ "key": "GADF..." }
]
},
{
"address": "GFDD...",
"identities": [
{ "role": "sender", "authenticated": true },
{ "role": "receiver" },
],
"signers": [
{ "key": "GADF..." }
]
},
{
"address": "GACD...",
"identities": [
{ "role": "sender" },
{ "role": "receiver", "authenticated": true },
],
"signers": [
{ "key": "GADF..." }
]
}
]
}
Building, hosting and using this system has numerous security concerns because a server is being delegated to be a signer of an account and will handle one or more signing keys for many accounts.
This list of concerns is not exhaustive.
Implementers should also consider JWT, encryption, secure coding, and general security best practices.
Implementers and users of the protocol must consider the risks of relying on alternative identities such as phone number or email to be definite proof of the identity of a client or user.
Implementers may choose to rotate the keys used as signers of the accounts for any reason, e.g. as a routine best practice, or in response to a compromised key. An implementor should maintain old signing keys so that accounts that have not added a new signing key are still able to use them.
A client can query the server for what signers the server has available for an account and should replace the signer on the account with the most recently generated signing key at its earliest convenience.
Implementers choose the external authentication methods supported by their implementation.
Implementers should consider the risks posed by the external authentication methods that are supported by their implementation. An implementer, and the client using the implementation, are responsible for evaluating whether the authentication method provides a level of security suitable for accounts that will be registered.
If an implementation supports a variety of external authentication methods the client must evaluate which are suitable for the account it is registering. For example, if a client considers one type of authentication to carry unacceptable risk for an account, it may choose to not use that form of authentication.
Clients can update an account registered, adding or removing external authentication methods. For example, if a client registered an account with a phone number and then later collects an email address from the account holder, the client could add email alongside the phone number, or it could add email and remove the phone number so that the account can only be recovered by email.
This protocol discusses a phone and email authentication provider vaguely but does not provide any specifics to that specification. This is an area still under research and once a better definition for what the specification is it will either be added to this protocol, added as a separate protocol proposal, or referenced if it is already captured as a standard elsewhere.
Recoverysigner is implemented by the Stellar recoverysigner service, recoverysigner.