---
title: "Sign and verify with Ed25519 in C#"
url: https://recursiveai.org/onecrypto/cookbook/ed25519-sign-verify/
description: "Sign and verify with Ed25519 in C# on .NET 10, which has no Ed25519 of its own: generate a key, sign, verify, save the keys as PEM, and check the signature with OpenSSL. Runnable code with OneCrypto 0.1.0-preview.1."
version: "0.1.0-preview.1"
---

# Sign and verify with Ed25519 in C#

.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.

```shell
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`:

```csharp
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);
```

```text
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:

```shell
openssl pkeyutl -verify -pubin -inkey public.pem -rawin \
  -in message.txt -sigfile message.sig
```

```text
Signature Verified Successfully
```

The 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](https://recursiveai.org/onecrypto/cookbook/load-pem-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](https://recursiveai.org/onecrypto/docs/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](https://recursiveai.org/onecrypto/docs/curves/).
- **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](https://recursiveai.org/onecrypto/cookbook/sign-file-cms/).
