X25519 is an elliptic curve Diffie-Hellman function on Curve25519 that combines a 32-byte private key with a 32-byte public value to produce a 32-byte shared secret. It is defined in RFC 7748 and targets roughly 128-bit security. You meet it in TLS 1.3 key exchange, where RFC 8446 says implementations should support it. A 32-byte private key, a 32-byte public key and a 32-byte shared secret are the sizes people search for.
X25519 works on the x-coordinate only, which RFC 7748 calls the u-coordinate. The field is the integers modulo the prime 2^255 - 19, and the curve has a cofactor of 8. Every value is a 32-byte string in little-endian order, and the standard base point is the byte 9 followed by 31 zero bytes.
Each side picks 32 random bytes as a private key. Before use, the key is "clamped" by decodeScalar25519:
The public key is X25519(private, 9). Alice sends hers to Bob, Bob sends his to Alice, and each computes X25519(own private, other public). Both arrive at the same 32 bytes. RFC 7748 says the receiver masks the top bit of the final byte of an incoming u-coordinate, and must accept non-canonical values by reducing them modulo the prime.
The shared secret is not a key yet. RFC 7748 tells you to feed it, together with both public keys, into a key derivation function such as HKDF. The example below runs the RFC 7748 test vector with Node.js.
const c = require('crypto');
const pk = h => c.createPrivateKey({ key: Buffer.concat([Buffer.from('302e020100300506032b656e04220420', 'hex'), Buffer.from(h, 'hex')]), format: 'der', type: 'pkcs8' });
const pub = k => c.createPublicKey(k).export({ format: 'der', type: 'spki' }).subarray(-32).toString('hex');
const a = pk('77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a');
const b = pk('5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb');
console.log('alice pub', pub(a));
console.log('bob pub ', pub(b));
const pubk = h => c.createPublicKey({ key: Buffer.concat([Buffer.from('302a300506032b656e032100', 'hex'), Buffer.from(h, 'hex')]), format: 'der', type: 'spki' });
console.log('shared', c.diffieHellman({ privateKey: a, publicKey: pubk(pub(b)) }).toString('hex'));
alice pub 8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a
bob pub de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f
shared 4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742
All three values match the RFC 7748 section 6.1 vectors. Swapping the roles gives the same shared secret.
An X25519 private key, public key and shared secret are each 32 bytes, which is 64 hex characters. The curve behind it is Curve25519, and the larger sibling X448 uses 56-byte values instead. Clamping changes the private key in memory, but you still store and transmit all 32 bytes.
No. X25519 agrees on a shared secret, and Ed25519 signs messages. Both use the same underlying curve family, but they are separate algorithms with different key uses, so do not sign with an X25519 key or exchange with a signing key.