Cryptography

ed25519, SHA-256, blake2b PoW. Cross-implementation compatibility (Go ↔ JS).


XE uses standard cryptographic primitives with cross-implementation compatibility between Go and JavaScript (tweetnacl/blakejs).

Key Generation

Ed25519 via crypto/rand or deterministic from 32-byte seed.

Signing Contexts

  • Account address — sha256("xe/account/v1" ‖ pubkey). The address commits to the key rather than being it, so a credential can be rotated without the identity changing. Multisig addresses are the SHA-256 of the canonical keyset instead.
  • Block hashing — SHA-256(networkID ‖ canonical bytes ‖ aux bytes). The aux tail is a sequence of 8-byte-big-endian-length-prefixed sections: the opening block's pub_key under the tag xe/block/pubkey/v1 (ASCII hex, present only when previous == "0"), then, on lease-family blocks, the certificate hash and the timekeeper attestations sorted by public key. It is empty for any other block, so a plain non-opening send hashes exactly as it did before keys were split from addresses. Genesis blocks are hashed with the network-ID prefix cleared — they are created before the network ID is known.
  • Block signing — ed25519.Sign(privateKey, hash)
  • Vote signing — ed25519 over canonical vote encoding
  • Attestation signing — ed25519 over SHA-256(leaseHash || timestamp)
  • Chat signing — ed25519 over the envelope id = sha256(f(from) ‖ f(pub_key) ‖ f(to) ‖ f(message) ‖ u64be(timestamp)) where f(x) is a 4-byte big-endian length followed by the UTF-8 bytes; the same id is the PoW target
  • Chat read proof — ed25519.Sign(priv, SHA-256("xe/chat-read-auth/v1\0" ‖ challenge_bytes)) over a single-use, 120-second challenge from GET /chat/auth/challenge
  • Directory signing — ed25519.Sign(priv, SHA-256("xe/directory-registration/v1\x00" ‖ len‖networkID ‖ len‖account ‖ len‖pubKey ‖ len‖nodePeer ‖ len‖timestamp)), where each len is an 8-byte big-endian length prefix framing the field that follows; the node checks pubKey derives account before verifying

Proof of Work

Anti-spam only, not consensus. Always computed client-side.

result = blake2b_8(nonce_LE || blockHash)
valid  = result >= difficulty
  • Block DefaultDifficulty: 0xfffff80000000000 (~2²¹ attempts, ~1s)
  • Chat DefaultPoWDifficulty: 0xffffc00000000000 (~2¹⁸ attempts — chat spam pricing is tuned independently of block mining)
  • TestDifficulty: 0x0000000000000002 (instant)
  • Nonce: little-endian; result compared as big-endian
  • Both difficulties are advertised in GET /node

Functions: ComputePoW, ComputePoWConcurrent, ComputePoWWithContext, ValidatePoW