A pure C# implementation of the Ed25519 digital signature algorithm, ported from the ref10 reference implementation and the improved C port by Orson Peters (orlp/ed25519).
- RFC 8032 compliant - Passes all standard test vectors
- Pure C# - No native dependencies
- High performance - Uses precomputed tables for fast scalar multiplication
- PKCS#8 / SPKI support - Import/export keys in standard formats
- PEM support - Read and write PEM-encoded keys
dotnet add package Ed25519using Ed25519;
Span<byte> publicKey = stackalloc byte[32];
Span<byte> privateKey = stackalloc byte[64];
Span<byte> seed = stackalloc byte[32];
// Fill seed with 32 bytes of cryptographically secure random data
RandomNumberGenerator.Fill(seed);
Ed25519.CreateKeypair(publicKey, privateKey, seed);- Seed (32 bytes): the secret input to RFC 8032 key generation. This library’s PKCS#8 helpers encode/decode the seed.
- Public key (32 bytes): the encoded curve point.
- Private key (64 bytes): this library’s “expanded” private key, derived as
SHA-512(seed)and stored as:privateKey[0..31]: clamped scalarprivateKey[32..63]: prefix
Ed25519.Sign(...) expects the expanded 64-byte private key (and optionally the public key).
- Deterministic signatures: Ed25519 signing is deterministic for a given (expanded) private key and message (no external randomness needed).
- Constant-time vs variable-time:
- Signing uses constant-time building blocks (
CMov, etc.). - Verification uses variable-time operations (inputs are public).
- Signing uses constant-time building blocks (
byte[] message = "Hello, World!"u8.ToArray();
Span<byte> signature = stackalloc byte[64];
Ed25519.Sign(signature, message, privateKey);bool isValid = Ed25519.Verify(signature, message, publicKey);// Export private key seed to PKCS#8 (DER)
byte[] pkcs8 = Pkcs.EncodePkcs8PrivateKey(seed);
// Import seed from PKCS#8 (DER)
byte[] importedSeed = Pkcs.DecodePkcs8PrivateKey(pkcs8);
// Re-expand to this library's 64-byte private key format (and compute public key)
Span<byte> importedPrivateKey = stackalloc byte[64];
Span<byte> importedPublicKey = stackalloc byte[32];
Ed25519.CreateKeypair(importedPublicKey, importedPrivateKey, importedSeed);
// Export public key to SPKI (DER)
byte[] spki = Pkcs.EncodeSubjectPublicKeyInfo(publicKey);
// Import public key from SPKI (DER)
byte[] importedPubKey = Pkcs.DecodeSubjectPublicKeyInfo(spki);// Export to PEM
string privatePem = Pkcs.ExportPrivateKeyPem(seed); // "PRIVATE KEY" (PKCS#8)
string publicPem = Pkcs.ExportPublicKeyPem(publicKey); // "PUBLIC KEY" (SPKI)
// Import from PEM (example: private key)
byte[] privateDer = Pkcs.DecodePem(privatePem, "PRIVATE KEY");
byte[] importedSeed = Pkcs.DecodePkcs8PrivateKey(privateDer);While Ed25519 keys can be used for signing, .NET's X509Certificate2 has limited support for Ed25519. For self-signed certificates with Ed25519, you may need to:
- Generate the Ed25519 keypair using this library
- Manually construct the TBS (To-Be-Signed) certificate structure
- Sign it with
Ed25519.Sign() - Encode the final certificate in DER/PEM format
If your goal is certificate issuance, this repo includes CSR (PKCS#10) helpers in Pkcs:
Pkcs.EncodePkcs10CertificationRequest(...)(DER)Pkcs.ExportCsrPem(...)(PEM)Pkcs.VerifyPkcs10CertificationRequest(...)
It also supports exporting encrypted PKCS#8 PEM ("ENCRYPTED PRIVATE KEY") via Pkcs.ExportEncryptedPrivateKeyPem(...).
This implementation follows the ref10 naming conventions from the original C code. The short names preserve traceability to the reference implementation for auditing purposes.
| Type | Full Name | Description |
|---|---|---|
Fe |
Field Element | Element of the field Z/(2²⁵⁵-19), represented as 10 limbs of 26/25 bits |
Sc |
Scalar | Integer modulo L (the group order ≈ 2²⁵²), used for secret keys |
Ge |
Group Element | Static class for curve point operations |
GeP2 |
Projective Point | Point in (X:Y:Z) projective coordinates where x=X/Z, y=Y/Z |
GeP3 |
Extended Point | Point in (X:Y:Z:T) extended coordinates where T=XY/Z |
GeP1P1 |
Completed Point | Intermediate form (X:Y:Z:T) output by additions, with x=X/Z, y=Y/T |
GePrecomp |
Precomputed Point | Affine Niels form (y+x, y-x, 2dxy) for fast fixed-base scalar multiplication |
GeCached |
Cached Point | Extended Niels form for efficient point addition |
Two precomputed tables accelerate scalar multiplication:
Base[32,8]: Contains(j+1) × 256^i × Bfori ∈ [0,31],j ∈ [0,7]. Used byScalarMultBasefor signing.Bi[8]: Contains(2i+1) × Bfori ∈ [0,7]. Used byDoubleScalarMultVartimefor verification.
The implementation uses constant-time conditional moves (CMov, CSwap) to prevent timing side-channels during signing. Verification uses variable-time operations since all inputs are public.
- Curve: Edwards curve
-x² + y² = 1 + dx²y²whered = -121665/121666 - Base point B: The standard Ed25519 generator
- Group order L:
2²⁵² + 27742317777372353535851937790883648493
MIT License