X25519
Elliptic curve Diffie Hellman key exchange on Curve25519. Use this to create shared secrets between two parties who only know each other's public keys.
local X25519 = Cryptography.Verification.EdDSA.X25519
Masked Key Operations
X25519 in this library uses masked keys for additional security. The masking adds a layer of protection against certain side channel attacks.
X25519.Mask(SecretKey: buffer) -> buffer
Creates a masked key from a secret key.
local SecretKey = CSPRNG.Ed25519Random()
local MaskedKey = X25519.Mask(SecretKey)
X25519.MaskSignature(SignatureSecretKey: buffer) -> buffer
Creates a masked key from an Ed25519 signature secret key, allowing you to use the same key material for both signing and key exchange.
local EdSecretKey = CSPRNG.Ed25519Random()
local MaskedKey = X25519.MaskSignature(EdSecretKey)
X25519.Remask(MaskedKey: buffer) -> buffer
Refreshes the mask on an already masked key.
local RemaskedKey = X25519.Remask(MaskedKey)
X25519.PublicKey(MaskedKey: buffer) -> buffer
Derives a 32 byte public key from a masked key.
local PublicKey = X25519.PublicKey(MaskedKey)
X25519.MaskComponent(MaskedKey: buffer) -> buffer
Extracts the mask component from a masked key.
local MaskComponent = X25519.MaskComponent(MaskedKey)
Key Exchange
X25519.Exchange(MaskedSecretKey: buffer, TheirPublicKey: buffer) -> (buffer, buffer)
Performs the key exchange. Returns two buffers.
-- Alice generates her keypair
local AliceSecret = CSPRNG.Ed25519Random()
local AliceMasked = X25519.Mask(AliceSecret)
local AlicePublic = X25519.PublicKey(AliceMasked)
-- Bob generates his keypair
local BobSecret = CSPRNG.Ed25519Random()
local BobMasked = X25519.Mask(BobSecret)
local BobPublic = X25519.PublicKey(BobMasked)
-- They exchange public keys (over an insecure channel)
-- Alice computes shared secret
local AliceShared1, AliceShared2 = X25519.Exchange(AliceMasked, BobPublic)
-- Bob computes shared secret
local BobShared1, BobShared2 = X25519.Exchange(BobMasked, AlicePublic)
Deriving Keys
The raw shared secret should not be used directly as an encryption key. Run it through a key derivation function:
local Shared1, Shared2 = X25519.Exchange(MyMasked, TheirPublic)
-- Derive encryption key using BLAKE3
local Context = buffer.fromstring("MyApp encryption key")
local KeyDeriver = Blake3.DeriveKey(Context)
local _, EncryptionKey = KeyDeriver(Shared1, 32)
Forward Secrecy
For forward secrecy, generate ephemeral keypairs for each session:
-- Session setup
local EphemeralSecret = CSPRNG.Ed25519Random()
local EphemeralMasked = X25519.Mask(EphemeralSecret)
local EphemeralPublic = X25519.PublicKey(EphemeralMasked)
-- Exchange ephemeral public keys with peer
-- Compute session key
local SessionShared1, _ = X25519.Exchange(EphemeralMasked, PeerEphemeralPublic)
local Context = buffer.fromstring("session key")
local SessionDeriver = Blake3.DeriveKey(Context)
local _, SessionKey = SessionDeriver(SessionShared1, 32)
If long term keys are later compromised, past session keys remain secure because the ephemeral secret keys are gone.
How It Works
X25519 uses the Montgomery curve Curve25519 (different representation than Ed25519's Edwards curve, but mathematically equivalent). The implementation uses Montgomery ladder for scalar multiplication and projective coordinates to avoid expensive field inversions.
The masking provides additional protection by splitting the secret key into shares, making it harder to extract through side channels.
Security Properties
Security level Around 128 bits
Small subgroup attacks The clamping prevents these by ensuring scalars are multiples of the cofactor (8)
Not authenticated X25519 alone does not prove you are talking to who you think. An active attacker can perform a man in the middle attack. Combine with signatures or a pre shared key for authentication.
Not quantum resistant A quantum computer could break X25519. Use ML KEM if you need post quantum security.
Authenticated Key Exchange
Combine X25519 with Ed25519 signatures:
-- Both parties have Ed25519 identity keys for signing
-- Both parties have X25519 keys for exchange
-- Alice signs her X25519 public key
local AliceSig = EdDSA.Sign(AliceX25519Public, AliceEdSecret, AliceEdPublic)
-- Bob verifies Alice's X25519 key came from Alice
local Valid = EdDSA.Verify(AliceX25519Public, AliceEdPublic, AliceSig)
if not Valid then error("Invalid signature") end
-- Now Bob can trust the X25519 exchange
local Shared1, Shared2 = X25519.Exchange(BobMasked, AliceX25519Public)