Skip to content

Latest commit

 

History

History
272 lines (213 loc) · 10.6 KB

sep-0023.md

File metadata and controls

272 lines (213 loc) · 10.6 KB

Preamble

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

Simple Summary

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.

Motivation

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.

Abstract

A new strkey format is introduced to represent both the public key and 64-bit ID of a MuxedAccount, as specified in CAP-0027.

Specification

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:

  1. Start with the appropriate version byte computed by the OR of a key type base value and algorithm selector from the tables above.

  2. Append the binary bytes of the key (e.g., 32-bytes for ED25519).

  3. If we are encoding a multiplexed address, append an 8-byte memo ID in network byte order (most significant byte first).

  4. 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).

  5. 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.

Horizon API changes

The following proposed Horizon API changes would expose multiplexed accounts in a backwards-compatible way:

  1. 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
    
  2. Queries for a multiplexed MuxedAccount (starting M...) return only operations that touch that particular multiplexed account ID.

  3. 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 not KEY_TYPE_MUXED_ED2551).

Implementation

https://github.com/xdrpp/stc

Tests

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 test cases

  1. 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,
    }
    
  2. 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,
     }
    
  3. 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,
     }
    

Invalid test cases

  1. Invalid length (Ed25519 should be 32 bytes, not 5)

    • Strkey: GAAAAAAAACGC6
  2. 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
  3. Invalid length (congruent to 1 mod 8)

    • Strkey: GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVSGZA
  4. Invalid length (base-32 decoding should yield 35 bytes, not 36)

    • Strkey: GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUACUSI
  5. Invalid algorithm (low 3 bits of version byte are 7)

    • Strkey: G47QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVP2I
  6. Invalid length (congruent to 6 mod 8)

    • Strkey: MA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVAAAAAAAAAAAAAJLKA
  7. Invalid length (base-32 decoding should yield 43 bytes, not 44)

    • Strkey: MA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVAAAAAAAAAAAAAAV75I
  8. Invalid algorithm (low 3 bits of version byte are 7)

    • Strkey: M47QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUAAAAAAAAAAAACJUQ
  9. Padding bytes are not allowed

    • Strkey: MA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUAAAAAAAAAAAACJUK===
  10. Invalid checksum

    • Strkey: MA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUAAAAAAAAAAAACJUO

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",
}

Design Rationale

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.

Security Concerns

Augmented strkey could potentially make it harder to see where payments are going, as different multiplexed accounts will reference the same underlying account.