Accounts & Keys

Ed25519 key pairs, addresses, signing, multisig, and delegation.


Every single-key XE account is an ed25519 key pair, but the address is not the public key. Both are 32 bytes / 64 lowercase hex, so nothing about the wire shape gives it away — the values differ, and substituting one for the other fails validation:

address = sha256("xe/account/v1" ‖ pubkey_32)

The address is the identity: it goes in a block's account, destination and representative, in /accounts/{address}/* paths, and in provider / consumer on leases. The public key is the credential: it appears as pub_key on a chain's first block, on directory registrations and on chat envelopes, and as signatures[].public_key on multisig blocks. Because SHA-256 is one-way, a key cannot be recovered from an address — anything that must verify a signature is handed the key alongside it. Committing to the key instead of being it is what allows a key to be rotated later without the account changing. No prefixes, no checksums.

Key Generation

kp, err := core.GenerateKeyPair()        // random
kp := core.KeyPairFromSeed(seed)         // deterministic from 32-byte seed
addr := kp.Address()                     // sha256("xe/account/v1" ‖ pubkey); core.DeriveAddress(pubKeyHex) for a bare key

xe wallet create prints both values and a 24-word recovery phrase (xe wallet phrase shows it again; xe wallet restore rebuilds the seed file from it).

Block Signing

  1. Canonical encoding via MarshalBlockCanonical()
  2. SHA-256(networkID ‖ canonical ‖ aux) → b.Hash — the aux tail (MarshalBlockAux) frames the opening block's pub_key (tag xe/block/pubkey/v1, present only when previous == "0") and, on lease-family blocks, the certificate hash plus the timekeeper attestations; it is empty for every other block. Genesis is hashed with the network-ID prefix cleared
  3. ed25519.Sign(privateKey, hashBytes) → b.Signature

The first block of a single-key chain must declare pub_key (the node checks it derives account); every later block must not — the key is already on the chain, and accepting a redeclaration would be a silent credential swap.

Multisig Accounts

  • Address = sha256(canonical(keyset)) (hash-derived)
  • Opened with multisig_open block
  • Rotated via multisig_update blocks
  • Spending: M-of-N threshold; Receiving: 1-of-N

Delegation

Each block includes an optional Representative field. Empty means keep current delegation. Delegation weight is the account's XE balance in micro-XE — XUSD is mintable by authorized minters and must not mint consensus weight, so it contributes nothing.