.NET has no Ed25519. OneCrypto adds it in managed code: Ed25519PrivateKey.Generate() makes a key, TrySign signs, and PublicKey.Verify checks. The program below also saves both keys as standard PEM files so OpenSSL can verify the same signature.
dotnet new console -o Ed25519Demo && cd Ed25519Demo dotnet add package RecursiveAI.OneCrypto --version 0.1.0-preview.1
OneCrypto is a preview, so dotnet add package needs --version or --prerelease. Put this in Program.cs and run dotnet run:
using System.Security.Cryptography; using System.Text; using RecursiveAI.OneCrypto.Pkcs; using RecursiveAI.OneCrypto.Primitives; byte[] message = Encoding.UTF8.GetBytes("Ship build 4127 to production."); File.WriteAllBytes("message.txt", message); // A new key pair; the secret sits in a pinned buffer that Dispose zeroes using Ed25519PrivateKey key = Ed25519PrivateKey.Generate(); byte[] signature = new byte[Ed25519.SignatureSize]; key.TrySign(message, signature, out int written); File.WriteAllBytes("message.sig", signature); Console.WriteLine($"Signature: {written} bytes"); // Verify with the public key alone Ed25519PublicKey publicKey = key.PublicKey; Console.WriteLine($"Valid: {publicKey.Verify(message, signature)}"); byte[] tampered = Encoding.UTF8.GetBytes("Ship build 4128 to production."); Console.WriteLine($"Tampered: {publicKey.Verify(tampered, signature)}"); // Save the keys as PKCS#8 and SubjectPublicKeyInfo PEM, as OpenSSL writes them using PrivateKeyInfo pkcs8 = PrivateKeyInfo.FromEd25519(key); File.WriteAllText("private.pem", pkcs8.ToPem()); string spki = PemEncoding.WriteString("PUBLIC KEY", pkcs8.GetPublicKey().Encoded.Span); File.WriteAllText("public.pem", spki); Console.WriteLine(spki);
Signature: 64 bytes Valid: True Tampered: False -----BEGIN PUBLIC KEY----- MCowBQYDK2VwAyEAWuJcyMUuwtde+4CAbyGKs6ZkrcDKjpGoJEvcTPzm35c= -----END PUBLIC KEY-----
Ed25519 signatures are always 64 bytes and the public key is 32. Signing is deterministic: the same key and message give the same signature. Verify returns false for any malformed input rather than throwing.
Check it with OpenSSL
The files are the standard formats, so any Ed25519 implementation can check the signature. With OpenSSL 3:
openssl pkeyutl -verify -pubin -inkey public.pem -rawin \ -in message.txt -sigfile message.sig
Signature Verified SuccessfullyThe other way round works too: Crypto.Load("key.pem") reads a key made with openssl genpkey -algorithm ed25519, and its PrivateKeys[0].Info.ToEd25519() signs with it (see Load a PEM or PKCS#8 private key).
Raw keys
If you store raw 32-byte keys, ExportSeed gives the private seed and Ed25519PrivateKey.FromSeed reads it back; PublicKey.ToArray() and Ed25519PublicKey.FromBytes do the same for the public key. The static Ed25519.TrySign(seed, …) and Ed25519.Verify(publicKey, …) work on spans without key objects.
Limits
- Not audited. This library has not been independently audited. It is a preview: 0.1.0-preview.1, and the API can change.
- Timing. The project’s timing tests cover Ed25519 signing only in “earlier, shorter runs”, and it makes “no constant-time claim … for key handling” yet. Verification is not constant time by design: it works on public data. Details: Conformance.
- Verification rules. OneCrypto rejects non-canonical public keys and signature values, as RFC 8032 asks, and checks the cofactorless equation. A few signatures that other libraries accept (ZIP-215 rules) fail here: Ed25519, Ed448, X25519 and X448.
- Speed. Against BouncyCastle 2.7.0, Ed25519 signing takes 0.55 of its time and verifying 0.90 on x64 (0.48 and 0.89 on arm64), from the project’s benchmark table. The x64 run is marked load-flagged.
Ed448 works the same way with Ed448PrivateKey. To sign documents rather than raw bytes, see Sign a file with CMS.