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 keyxe 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
- Canonical encoding via
MarshalBlockCanonical() SHA-256(networkID ‖ canonical ‖ aux)→b.Hash— the aux tail (MarshalBlockAux) frames the opening block'spub_key(tagxe/block/pubkey/v1, present only whenprevious == "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 cleareded25519.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_openblock - Rotated via
multisig_updateblocks - 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.