SEP: 0023
Title: Augmented strkey format for multiplexed accounts
Author: David Mazières, Tomer Weller <@tomerweller>, Leigh McCulloch <@leighmcculloch>, Alfonso Acosta <@2opremio>
Track: Standard
Status: Draft
Created: 2019-09-16
Updated: 2021-03-25
Version: 0.0.2
Discussion: https://groups.google.com/forum/#!forum/stellar-dev
Strkey is an ASCII format for representing Stellar account IDs and addresses. This document introduces augmented strkey to represent account IDs for multiplexed addresses introduced by CAP-0027.
A common pattern in the Stellar ecosystem is for services to share a
single Stellar account ID across many users or independent
transactions, relying on the memo ID to disambiguate incoming
payments. CAP-0027 introduced the MuxedAccount
type, which allows
an address to be paired with a 64-bit ID. This document extends the
Strkey format to allow the specification of an arbitrary
MuxedAccount
as a string. If widely adopted by the ecosystem, users
will be able to use custodial services and exchanges in much the same
way as wallets, by specifying a MuxedAccount
instead of a separate
AccountID
and Memo
.
A new strkey format is introduced to represent both the public key and
64-bit ID of a MuxedAccount
, as specified in CAP-0027.
Strkey, the ASCII encoding for accounts, keys, and signers, currently covers covered ED25519 public keys, ED25519 private keys (also known as seeds), pre-authorized transaction hashes, and hash-x signers (which provide signing authority upon revelation of a SHA-256 preimage). We make two changes: First, we add a new type of strkey for multiplexed account IDs, represented by a new value in the top 5 bits of the version byte, which is the 8 bits of the key. The possible "base" values (top 5 bits) of the version byte, which determine the first character of the base-32 encoding of the key, are listed here:
Key type | Base value | First char | Muxed | Alg |
---|---|---|---|---|
STRKEY_PUBKEY | 6 << 3 | G | no | PK |
STRKEY_MUXED | 12 << 3 | M | yes | PK |
STRKEY_PRIVKEY | 18 << 3 | S | no | PK |
STRKEY_PRE_AUTH_TX | 19 << 3 | T | no | Hash |
STRKEY_HASH_X | 23 << 3 | X | no | Hash |
Second, instead of requiring the low 3 bits of the version byte to be zero, we now use them as an algorithm specifier. Thus, a version byte becomes the bitwise OR of a base value above and one of the algorithm specifiers from the two tables below. (These tables will be extended when Stellar adds additional crypto algorithms.)
PK Algorithm | Value |
---|---|
STRKEY_ALG_ED25519 | 0 |
Hash Algorithm | Value |
---|---|
STRKEY_ALG_SHA256 | 0 |
The following steps transform a binary key into a strkey:
-
Start with the appropriate version byte computed by the OR of a key type base value and algorithm selector from the tables above.
-
Append the binary bytes of the key (e.g., 32-bytes for ED25519).
-
If we are encoding a multiplexed address, append an 8-byte memo ID in network byte order (most significant byte first).
-
Compute a 16-bit CRC16 checksum of the combined version byte, optional memo ID, and binary key (using polynomial x16 + x12 + x5 + 1). Append the two-byte checksum to the result of the previous step (e.g., producing a 35-byte quantity for a non-multiplexed ED25519 public key, or 43 byte quantity for a multiplexed one).
-
Encode the result of the previous step using RFC4648 base-32 encoding without padding. For example, a multiplexed address yields a 43-byte quantity whose base-32 encoding is 69 bytes with no trailing
=
signs because no padding is allowed.
To transform a strkey into a binary key, the process is simply
reversed. However, a strkey is only valid if re-encoding the binary
key yields the exact same strkey. Note, in particular, that a
strkey's length must not be congruent to 1, 3, or 6 mod 8, and
unused bits of the last symbol must be zero. Some non-padding base32
libraries, such as the one in the standard go
library—base32.StdEncoding.WithPadding(base32.NoPadding)
—do
not enforce these requirements. Therefore, implementations of strkey
decoding must check and reject such invalid inputs, or perform a
round-trip and reject strkey inputs that do not re-encode to the exact
same string.
The following proposed Horizon API changes would expose multiplexed accounts in a backwards-compatible way:
-
Anyplace a MuxedAccount appears, if the account is of a multiplexed type (currently just
KEY_TYPE_MUXED_ED2551
), two new fields are added to the JSON.-
Base field name +
_muxed
is the strkey of the multiplexed account. -
Base field name +
_muxed_id
is the integer.
For example, given the MuxedAccount
MAQAA5L65LSYH7CQ3VTJ7F3HHLGCL3DSLAR2Y47263D56MNNGHSQSAAAAAAAAAAE2LP26
, you might get the following fields:source_account: GAQAA5L65LSYH7CQ3VTJ7F3HHLGCL3DSLAR2Y47263D56MNNGHSQSTVY source_account_muxed: MAQAA5L65LSYH7CQ3VTJ7F3HHLGCL3DSLAR2Y47263D56MNNGHSQSAAAAAAAAAAE2LP26 source_account_muxed_id: 1234
-
-
Queries for a multiplexed MuxedAccount (starting M...) return only operations that touch that particular multiplexed account ID.
-
Queries for non-multiplexed accounts (starting G...) return all transactions affecting the account, including multiplexed subaccounts. As an exception, a new optional query parameter
nomux=1
returns only transaction operations that are to the non-multiplexed representation of the target account (e.g.,KEY_TYPE_ED25519
notKEY_TYPE_MUXED_ED2551
).
Implementations of strkey must accept the following valid test cases and reject the invalid test cases. Common bugs such as forgetting to reject strkeys with length congruent to 1, 3, or 6 mod 8 before invoking base-32 decoding will cause software to accept the invalid test cases, which could in turn cause security problems.
-
Valid non-multiplexed account
- Strkey
GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVSGZ
- type:
KEY_TYPE_ED25519
- Binary
MuxedAccount
:
{ 0x00, 0x00, 0x00, 0x00, 0x3f, 0x0c, 0x34, 0xbf, 0x93, 0xad, 0x0d, 0x99, 0x71, 0xd0, 0x4c, 0xcc, 0x90, 0xf7, 0x05, 0x51, 0x1c, 0x83, 0x8a, 0xad, 0x97, 0x34, 0xa4, 0xa2, 0xfb, 0x0d, 0x7a, 0x03, 0xfc, 0x7f, 0xe8, 0x9a, }
- Strkey
-
Valid multiplexed account
- Strkey:
MA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUAAAAAAAAAAAACJUQ
- type:
KEY_TYPE_MUXED_ED25519
- id: 0
- ed25519:
GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVSGZ
- Binary
MuxedAccount
:
{ 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x0c, 0x34, 0xbf, 0x93, 0xad, 0x0d, 0x99, 0x71, 0xd0, 0x4c, 0xcc, 0x90, 0xf7, 0x05, 0x51, 0x1c, 0x83, 0x8a, 0xad, 0x97, 0x34, 0xa4, 0xa2, 0xfb, 0x0d, 0x7a, 0x03, 0xfc, 0x7f, 0xe8, 0x9a, }
- Strkey:
-
Valid multiplexed account in which unsigned id exceeds maximum signed 64-bit integer
- Strkey:
MA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVAAAAAAAAAAAAAJLK
- type:
KEY_TYPE_MUXED_ED25519
- id: 9223372036854775808
- ed25519:
GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVSGZ
- Binary
MuxedAccount
:
{ 0x00, 0x00, 0x01, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x0c, 0x34, 0xbf, 0x93, 0xad, 0x0d, 0x99, 0x71, 0xd0, 0x4c, 0xcc, 0x90, 0xf7, 0x05, 0x51, 0x1c, 0x83, 0x8a, 0xad, 0x97, 0x34, 0xa4, 0xa2, 0xfb, 0x0d, 0x7a, 0x03, 0xfc, 0x7f, 0xe8, 0x9a, }
- Strkey:
-
Invalid length (Ed25519 should be 32 bytes, not 5)
- Strkey:
GAAAAAAAACGC6
- Strkey:
-
The unused trailing bit must be zero in the encoding of the last three bytes (24 bits) as five base-32 symbols (25 bits)
- Strkey:
MA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUAAAAAAAAAAAACJUR
- Strkey:
-
Invalid length (congruent to 1 mod 8)
- Strkey:
GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVSGZA
- Strkey:
-
Invalid length (base-32 decoding should yield 35 bytes, not 36)
- Strkey:
GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUACUSI
- Strkey:
-
Invalid algorithm (low 3 bits of version byte are 7)
- Strkey:
G47QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVP2I
- Strkey:
-
Invalid length (congruent to 6 mod 8)
- Strkey:
MA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVAAAAAAAAAAAAAJLKA
- Strkey:
-
Invalid length (base-32 decoding should yield 43 bytes, not 44)
- Strkey:
MA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVAAAAAAAAAAAAAAV75I
- Strkey:
-
Invalid algorithm (low 3 bits of version byte are 7)
- Strkey:
M47QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUAAAAAAAAAAAACJUQ
- Strkey:
-
Padding bytes are not allowed
- Strkey:
MA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUAAAAAAAAAAAACJUK===
- Strkey:
-
Invalid checksum
- Strkey:
MA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUAAAAAAAAAAAACJUO
- Strkey:
You can paste these invalid strkeys more conveniently into a unit test using the following array:
{
"GAAAAAAAACGC6",
"MA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUAAAAAAAAAAAACJUR",
"GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVSGZA",
"GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUACUSI",
"G47QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVP2I",
"MA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVAAAAAAAAAAAAAJLKA",
"MA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVAAAAAAAAAAAAAAV75I",
"M47QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUAAAAAAAAAAAACJUQ",
"MA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUAAAAAAAAAAAACJUK===",
"MA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUAAAAAAAAAAAACJUO",
}
If widely adopted by the ecosystem, naming a multiplexed account with a single string will allow multiplexed accounts to be used as end-user addresses.
Augmented strkey could potentially make it harder to see where payments are going, as different multiplexed accounts will reference the same underlying account.