---
title: "Ed25519 PDF signature in C#"
url: https://recursiveai.org/onecrypto/cookbook/ed25519-pdf-signature/
description: "Sign a PDF with an Ed25519 key in C# (PAdES, ISO 32002), and with ML-DSA post-quantum keys as a preview, then verify it. Checked with OpenSSL and pyHanko. Runnable code with OneCrypto.Pdf 0.2.0-preview.1."
version: "0.2.0-preview.1"
---

# Ed25519 PDF signature in C#

.NET can’t sign a PDF with Ed25519: it has no Ed25519, and OnePDF’s own signer takes RSA and ECDSA keys. `RecursiveAI.OneCrypto.Pdf` signs with any OneCrypto key, including Ed25519, Ed448 and, as a preview, the post-quantum ML-DSA. The program below signs the IRS Form W-9 once with an Ed25519 key and once with an ML-DSA-65 key, both made by OpenSSL, and verifies each.

```shell
dotnet new console -o PdfEd25519 && cd PdfEd25519
dotnet add package RecursiveAI.OneCrypto.Pdf --version 0.2.0-preview.1
curl -LO https://www.irs.gov/pub/irs-pdf/fw9.pdf
for alg in ed25519 ML-DSA-65; do
  openssl req -x509 -newkey $alg -nodes -days 365 -subj "/CN=Ada Lovelace" \
    -addext "basicConstraints=critical,CA:FALSE" \
    -addext "keyUsage=critical,digitalSignature,nonRepudiation" \
    -keyout $alg-key.pem -out $alg.pem
done
```

OneCrypto is a preview, so `dotnet add package` needs `--version` or `--prerelease`. ML-DSA keys need OpenSSL 3.5 or later, for the `openssl` command and for .NET on Linux. Put this in `Program.cs` and run `dotnet run`:

```csharp
using OnePdf;
using RecursiveAI.OneCrypto;
using RecursiveAI.OneCrypto.Pdf;
using RecursiveAI.OneCrypto.Validation;

foreach (string alg in new[] { "ed25519", "ML-DSA-65" })
{
    using LoadedItems certFile = Crypto.Load($"{alg}.pem");
    using LoadedItems keyFile = Crypto.Load($"{alg}-key.pem");
    Certificate cert = certFile.Certificates[0];

    using (PdfDocument doc = PdfDocument.Open("fw9.pdf",
        new PdfOpenOptions { AllowEditing = true }))
    using (FileStream output = File.Create($"fw9-{alg}.pdf"))
        doc.SignPades(output, keyFile.PrivateKeys[0].Signing!, cert);

    // Verify: the test certificate is its own trust anchor
    using PdfDocument signed = PdfDocument.Open($"fw9-{alg}.pdf");
    PadesReport report = signed.VerifyPades(new PadesVerifyOptions
    {
        TrustAnchors = [TrustAnchor.FromCertificate(cert)],
        ReferenceTime = DateTimeOffset.UtcNow,
    });
    PadesSignatureReport s = report.Signatures[0];
    Console.WriteLine($"{alg}: valid={s.IsValid}, level={s.Level}, " +
        $"OnePDF's own check: {s.OnePdf.SignatureCheck}");

    // For openssl cms -verify: the CMS signature and the bytes it covers
    PdfSignature sig = signed.GetSignatures().Single(x => x.FieldName == s.FieldName);
    File.WriteAllBytes($"{alg}.p7s", SignedData.Parse(sig.Contents, null, true)
        .Encoded.ToArray());
    using Stream signedBytes = sig.OpenSignedData()!;
    using FileStream copy = File.Create($"{alg}.bin");
    signedBytes.CopyTo(copy);
}
```

```text
ed25519: valid=True, level=Basic, OnePDF's own check: NotChecked
ML-DSA-65: valid=True, level=Basic, OnePDF's own check: NotChecked
```

OnePDF reports EdDSA and ML-DSA signatures as `NotChecked`, so for these keys the verdict is the bridge’s: it checks the CMS signature, the byte ranges and the certificate itself, and its `onepdf` check is a warning that says so. The signature uses SHA-512 for Ed25519 and ML-DSA, SHAKE256 for Ed448, and the sub-filter `ETSI.CAdES.detached`.

## Check it with OpenSSL and pyHanko

Both verify the Ed25519 and the ML-DSA signature. The CMS here has signed attributes, which OpenSSL needs for EdDSA:

```shell
for alg in ed25519 ML-DSA-65; do
  openssl cms -verify -binary -inform DER -in $alg.p7s -content $alg.bin \
    -CAfile $alg.pem -purpose any -out /dev/null 2>&1
done
python3 -m venv venv && venv/bin/pip install -q pyhanko pyhanko-cli
for alg in ed25519 ML-DSA-65; do
  venv/bin/pyhanko sign validate --trust-replace --trust $alg.pem \
    --executive-summary fw9-$alg.pdf 2>/dev/null
done
```

```text
CMS Verification successful
CMS Verification successful
Signature1:32484f370d674cacf027977b5dba0d116e22ef61f37f184febd0d4aeb7516661:VALID
Signature1:59b94b334dee1df4f1269ac698d7a3ef02f069d4a51698b90f3294132d18d411:VALID
```

Poppler’s `pdfsig` 26.01 reports both files as “Signature is Invalid”, although OpenSSL and pyHanko verify the same signatures. It says the same of an Ed25519 signature made by pyHanko, so read that line as pdfsig not verifying EdDSA or ML-DSA signatures, not as a fault in the file.

## Which readers accept them

- **Ed25519 and Ed448** are part of PDF 2.0 through ISO/TS 32002. A reader that implements it can verify them; older readers report the signature as unverifiable. The project records Acrobat’s behavior in its own sample run; its conformance table lists “Adobe Acrobat Reader: pending”, so there is no claim yet that Acrobat accepts them.
- **ML-DSA** in PDF isn’t standardized yet. It is a preview in OneCrypto, and the project says to expect no PDF reader to verify it yet.
- **RSA and ECDSA** are what every reader checks. Use them when the file goes to people whose software you don’t know.

## Limits

- **Not audited.** OneCrypto (cryptography, not cryptocurrency) has not been independently audited. It is a preview: 0.2.0-preview.1, and the API can change.
- **The bridge alone.** In the project’s conformance table, EdDSA and ML-DSA signatures “are judged by the bridge alone (OnePDF does not check them); ML-DSA is a preview”.
- **Platforms.** Ed25519 and Ed448 are OneCrypto’s own code and work everywhere .NET 10 runs. ML-DSA comes from the platform: Linux with OpenSSL 3.5 and recent Windows builds have it, macOS and the browser don’t, and there OneCrypto raises `AlgorithmNotSupportedException` ([Platform availability](https://recursiveai.org/onecrypto/docs/availability/)).
- **Trust.** A self-signed test certificate is its own trust anchor here. A valid report says the signature and certificate check out against the anchors you gave, nothing more.

How signing and verification work: [PDF signatures](https://recursiveai.org/onecrypto/docs/pdf/). Ed25519 outside PDF: [Sign and verify with Ed25519](https://recursiveai.org/onecrypto/cookbook/ed25519-sign-verify/).
