Skip to content

Cryptography

The XE network uses a small set of well-established cryptographic primitives. All implementations -- Go node, CLI client, web wallet -- must produce identical outputs for the same inputs.

Key generation

Algorithm: Ed25519

Two modes of key generation:

Function Description
GenerateKeyPair() Generate a random ed25519 keypair using crypto/rand
KeyPairFromSeed(seed) Deterministic derivation from a 32-byte seed. Panics on invalid seed length.
TryKeyPairFromSeed(seed) Like KeyPairFromSeed but returns an error instead of panicking for invalid seed length. Use for untrusted input.

KeyPairFromSeed always produces the same keypair for a given seed. This is how the standalone CLI and web wallet derive keys -- the user stores only the seed.

Account addresses

An account address is the hex-encoded ed25519 public key -- 64 hexadecimal characters (32 bytes).

Example: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2

There is no checksum, prefix, or other encoding. The raw public key bytes in hex are the address.

Hashing

Two hash functions are used for different purposes:

Algorithm Use Digest size
SHA-256 Block content hashing 32 bytes
Blake2b Proof of work 8 bytes (truncated)

Block hashing

The block hash is SHA-256(canonical_bytes) where canonical_bytes is the output of MarshalBlockCanonical. The hash is hex-encoded for storage and display.

Important

The canonical encoding excludes the PoW nonce. The hash covers only the content that the account holder signs. See Binary Encoding for the exact byte layout.

Signing

Block signing

Ed25519 signatures over the SHA-256 hash of canonical block bytes.

hash      = SHA-256(MarshalBlockCanonical(block))
signature = ed25519.Sign(privateKey, hash)

SignBlock computes the hash and signs in one step. VerifyBlock recomputes the hash from the block fields and verifies the signature against the account's public key (the account address, hex-decoded).

Vote signing

Representatives sign votes over the canonical vote binary encoding:

payload   = EncodeVote(vote)  // without the signature fields
signature = ed25519.Sign(repPrivateKey, payload)

See Binary Encoding for the vote wire format.

Attestation signing

Timekeepers sign lease attestations:

data      = SHA-256(leaseHash || timestamp)
signature = ed25519.Sign(timekeeperKey, data)

Where || denotes concatenation and timestamp is the attestation timestamp as a string.

Chat signing

P2P chat messages carry ed25519 detached signatures over the message envelope (serialized message content). This proves the sender's identity without requiring the recipient to trust the relay.

Directory signing

Account directory registrations are signed:

data      = account + timestamp
signature = ed25519.Sign(privateKey, data)

Where account is the hex address and timestamp is a string representation.

State chain signing

DAO state chain blocks are signed over the SHA-256 hash of the serialized block operations:

hash      = SHA-256(serializedOperations)
signature = ed25519.Sign(memberKey, hash)

Multiple DAO members sign the same hash to reach the signing threshold.

Web wallet implementation

The web wallet uses JavaScript equivalents that must produce byte-identical results:

Go JavaScript Purpose
crypto/ed25519 tweetnacl.js Key generation, signing, verification
crypto/sha256 Web Crypto API (subtle.digest) Block hashing
golang.org/x/crypto/blake2b blakejs Proof of work

Cross-implementation compatibility

The canonical encoding must match byte-for-byte between Go and JavaScript. A single byte difference in the canonical form produces a completely different hash and an invalid signature. Both implementations use VERSION_BYTE = 0x02, identical type bytes, and identical field ordering.

Summary

┌─────────────────────────────────────────────┐
│              Cryptographic Flow              │
├─────────────────────────────────────────────┤
│                                             │
│  seed (32 bytes)                            │
│    │                                        │
│    ▼                                        │
│  KeyPairFromSeed(seed)                      │
│    ├── publicKey  (32 bytes) = address      │
│    └── privateKey (64 bytes)                │
│                                             │
│  Block signing:                             │
│    canonical = MarshalBlockCanonical(block)  │
│    hash = SHA-256(canonical)                │
│    sig  = ed25519.Sign(privateKey, hash)    │
│                                             │
│  Block verification:                        │
│    hash = SHA-256(MarshalBlockCanonical(b)) │
│    ok   = ed25519.Verify(pubKey, hash, sig) │
│                                             │
│  Proof of work:                             │
│    result = Blake2b_8(nonce_LE || hash)     │
│    valid  = result >= difficulty            │
│                                             │
└─────────────────────────────────────────────┘

See also