Ed25519

From Crypto++ Wiki
Jump to: navigation, search
Ed25519
Documentation
#include <cryptopp/xed25519.h>

Ed25519 is a deterministic signature scheme using curve25519 by Daniel J. Bernstein, Niels Duif, Tanja Lange, Peter Schwabe and Bo-Yin Yang. The signature scheme uses curve25519, and is about 20x to 30x faster than Certicom's secp256r1 and secp256k1 curves. Also see High-speed high-security signatures (20110926).

ed25519 is unique among signature schemes. The signature scheme does not accumulate a digested message and then sign a representation of the digested message. Instead ed25519 accumulates the full undigested message and then uses it in the calculation of two [mostly] independent parameters [math]r[/math] and [math]S[/math]. The design presents challenges for large messages.

The Crypto++ library uses Andrew Moon's constant time ed25519-donna. The curve25519 gear appears to be like most other comparable public key objects in the Crypto++ library but it is mostly a facade. The Crypto++ classes are just wrappers around Moon's code that present some of the expected interface for callers. A side effect of the integration is, there is no general Point, Curve, or GroupParameters so you can't perform arbitrary calculations with curve25519.

To use ed25519 in your code include the header file xed25519.h. The name was selected because the header includes both x25519 and ed25519, and the name should be unique and avoid collisions. The objects you will primarily use are ed25519::Signer and ed25519::Verifier.

Andrew Moon's code is in the donna source files, and directly accessible in the Donna namespace. The header of interest is donna.h, and the functions of interest are ed25519_publickey, ed25519_sign and ed25519_sign_open. However, we recommend you use high level Crypto++ objects rather than the low level Donna code.

The Donna code is inherently little-endian due to design choices by the Bernstein team. If an ed25519 object takes or returns an Integer, then the library reverses they bytes for use in the Donna code. If an ed25519 object takes or returns a byte array, then the array is little-endian and the Donna code uses it directly.

ed25519 signatures are designed around small messages, like 128-bytes or 4 KB. Larger messages, like a 4.4 GB ISO file, will probably cause trouble. See the section Large Files for a discussion about it.

ed25519 uses SHA512 as the hash. It is hard wired into the source files and there is no way to change it without recompiling sources. In the future we may add overloaded functions that allow the caller to specify a HashTransformation.

Also see Keys and Formats and Curve25519 keys on the Crypto++ wiki; and Add ed25519 for modern signatures and ed25519 needs a SignStream and VerifyStream functions in the Crypto++ issue tracker.

Generating Keys

Generating a key is as simple as the following. All code paths that generate a private key will clamp the key.

AutoSeededRandomPool prng;
ed25519::Signer signer;
signer.AccessPrivateKey().GenerateRandom(prng);
	
const ed25519PrivateKey& privKey = dynamic_cast<const ed25519PrivateKey&>(signer.GetPrivateKey());
const Integer& x = privKey.GetPrivateExponent();
std::cout << x << std::endl;

Running the program produces output similar to the following.

4cc9cd70d755484327b5164fa8f3f080b12aea9cbcc7bf0d4e7d92f58d4ae990h

As stated in the introduction, the Integer means you are seeing a big-endian presentation, with the most significant byte on the left. Internally, the Donna code really uses a little-endian byte array that is reversed. That is, the internal byte array has the the least significant byte on the left and is 0x90, 0xe9, ..., 0xc9, 0x4c.

Given a private key you can create a public key with the following code.

ed25519::Verifier verifier(signer);

const ed25519PublicKey& pubKey = dynamic_cast<const ed25519PublicKey&>(verifier.GetPublicKey());
const Integer& y = pubKey.GetPublicElement();
std::cout << std::hex << y << std::endl;

Running the program produces output similar to the following.

7d8ce6951efa7d471f6109e3b16d1a02382fc2e01843df3ed44226c27e3a1733h

And again, as stated in the introduction, the Integer means you are seeing a big-endian presentation.

Saving Keys

You can save private keys in PKCS #8 or Asymmetric Key Package format. You can save public keys in X.509 or Asymmetric Key Package format. Asymmetric Key Packages are a superset of PKCS #8 and X.509, and specified in RFC 5958.

The RFCs throw a curve ball with respect to presentation. Rather than using network byte ordering which is big-endian, they use little-endian for the ASN.1 presentation. That means the BIT STRING and OCTET STRING shown below are little-endian, and not big-endian like most ASN.1 data.

To save a private or public key perform the following. Note that the code below simply prints the hex encoded key to stdout.

AutoSeededRandomPool prng;
HexEncoder encoder(new FileSink(std::cout));

ed25519::Signer signer;
signer.AccessPrivateKey().GenerateRandom(prng);
	
std::cout << "Private key: ";
signer.GetPrivateKey().Save(encoder);
std::cout << "\n" << std::endl;
	
ed25519::Verifier verifier(signer);

std::cout << "Public key: ";
verifier.GetPublicKey().Save(encoder);
std::cout << "\n" << std::endl;

The program produces the following output.

Private key: 302E020100300506032B657004220420C8525411752CAA85D81E5600BF5A0CF08E7
77CD36C3187E91FDB1FA1180FEC79

Public key: 302A300506032B657003210095F3B5CE87755462869A9F983874DFC8A91818A75C2A
3BBF2022DE7E06BD0361

You can save to a file with the following code.

AutoSeededRandomPool prng;

ed25519::Signer signer;
signer.AccessPrivateKey().GenerateRandom(prng);

FileSink fs1("private.key.bin");
signer.GetPrivateKey().Save(fs1);

ed25519::Verifier verifier(signer);

FileSink fs2("public.key.bin");
verifier.GetPublicKey().Save(fs2);

A run of the code produces the following output.

$ ./test.exe

$ dumpasn1 private.key.bin
  0  46: SEQUENCE {
  2   1:   INTEGER 0
  5   5:   SEQUENCE {
  7   3:     OBJECT IDENTIFIER curveEd25519 (1 3 101 112)
       :     }
 12  34:   OCTET STRING, encapsulates {
 14  32:     OCTET STRING
       :       38 F0 0C F9 9E 8F 4B E5 58 69 E6 C8 98 85 48 B6
       :       52 BB CC DE DF C6 F9 96 7A 92 9A 76 AB ED 25 62
       :     }
       :   }
0 warnings, 0 errors.

$ dumpasn1 public.key.bin
  0  42: SEQUENCE {
  2   5:   SEQUENCE {
  4   3:     OBJECT IDENTIFIER curveEd25519 (1 3 101 112)
       :     }
  9  33:   BIT STRING
       :     CC 43 DE 8C 98 58 6A BF 35 73 21 D5 32 AC 66 59
       :     54 CC 08 C4 2D 0E DE C4 D7 DE 11 32 28 01 96 29
       :   }
0 warnings, 0 errors.

Loading Keys

You can load private keys in PKCS #8 or Asymmetric Key Package format. You can load public keys in X.509 or Asymmetric Key Package format. Asymmetric Key Packages are a superset of PKCS #8 and X.509, and specified in RFC 5958.

The code below loads the private and public key and then validates them to ensure they are fit for service.

FileSource fs1("private.key.bin", true);
ed25519::Signer signer;
signer.AccessPrivateKey().Load(fs1);

bool valid = signer.GetPrivateKey().Validate(prng, 3);
if (valid == false)
    throw std::runtime_error("Invalid private key");

FileSource fs2("public.key.bin", true);
ed25519::Verifier verifier;
verifier.AccessPublicKey().Load(fs2);

valid = verifier.GetPublicKey().Validate(prng, 3);
if (valid == false)
    throw std::runtime_error("Invalid public key");

std::cout << "Keys are valid" << std::endl;

Running the code on the previous keys produces the message "Keys are valid" as expected.

Be careful when loading some keys, like those found in the RFCs. The keys are not clamped and fail validation.

Key Validation

You should always validate keys that you did not generate, including keys loaded via methods like Load and BERDecode. You should refrain from trusting the work of others. Trust is something to fall back to when you don't have security controls to place. In the case of private keys you do have controls to use.

Here is how the library validates ed25519 private keys. The level 3 check is expensive because it performs a pairwise consistency check by performing the scalar multiplication and comparing the calculated public key to the original public key.

bool ed25519PrivateKey::Validate(RandomNumberGenerator &rng, unsigned int level) const
{
    CRYPTOPP_UNUSED(rng);

    if (level >= 1 && IsSmallOrder(m_pk) == true)
        return false;
    if (level >= 3)
    {
        // Verify m_pk is pairwise consistent with m_sk
        SecByteBlock pk(PUBLIC_KEYLENGTH);
        SecretToPublicKey(pk, m_sk);

        if (VerifyBufsEqual(pk, m_pk, PUBLIC_KEYLENGTH) == false)
            return false;
    }

    return true;
}

ed25519 public keys are not validated because all points are valid and a pairwise consistency check requires the private key. The Validate function always returns true for public keys.

Using Integers

Earlier the following private key was shown. It is a random key that was serialized using PKCS #8 or Asymmetric Key Package format.

$ dumpasn1 private.key.bin
  0  46: SEQUENCE {
  2   1:   INTEGER 0
  5   5:   SEQUENCE {
  7   3:     OBJECT IDENTIFIER curveEd25519 (1 3 101 112)
       :     }
 12  34:   OCTET STRING, encapsulates {
 14  32:     OCTET STRING
       :       38 F0 0C F9 9E 8F 4B E5 58 69 E6 C8 98 85 48 B6
       :       52 BB CC DE DF C6 F9 96 7A 92 9A 76 AB ED 25 62
       :     }
       :   }
0 warnings, 0 errors.

The IETF used little-endian presentation and the following does not work as expected:

// OCTET STRING from private key
const byte key[] = {
    0x38, 0xF0, 0x0C, 0xF9, 0x9E, 0x8F, 0x4B, 0xE5,
    0x58, 0x69, 0xE6, 0xC8, 0x98, 0x85, 0x48, 0xB6,
    0x52, 0xBB, 0xCC, 0xDE, 0xDF, 0xC6, 0xF9, 0x96,
    0x7A, 0x92, 0x9A, 0x76, 0xAB, 0xED, 0x25, 0x62
};

Integer x(key, sizeof(key));

If you want to load a little-endian array into an Integer then use the following overload. The integer will parse the byte array in reverse.

// OCTET STRING from private key
const byte key[] = {
    0x38, 0xF0, 0x0C, 0xF9, 0x9E, 0x8F, 0x4B, 0xE5,
    0x58, 0x69, 0xE6, 0xC8, 0x98, 0x85, 0x48, 0xB6,
    0x52, 0xBB, 0xCC, 0xDE, 0xDF, 0xC6, 0xF9, 0x96,
    0x7A, 0x92, 0x9A, 0x76, 0xAB, 0xED, 0x25, 0x62
};

Integer x(key, sizeof(key), UNSIGNED, LITTLE_ENDIAN_ORDER);

Or manually reverse the array before creating the Integer as shown below.

// OCTET STRING from private key
byte key[] = {
    0x38, 0xF0, 0x0C, 0xF9, 0x9E, 0x8F, 0x4B, 0xE5,
    0x58, 0x69, 0xE6, 0xC8, 0x98, 0x85, 0x48, 0xB6,
    0x52, 0xBB, 0xCC, 0xDE, 0xDF, 0xC6, 0xF9, 0x96,
    0x7A, 0x92, 0x9A, 0x76, 0xAB, 0xED, 0x25, 0x62
};

std::reverse(key, key+sizeof(key));

Integer x(key, sizeof(key));

Message Signing

There are two ways to sign a message. First you can use the SignMessage member function. Second you can use a pipeline. Examples of both are shown below.

To sign a message using the SignMessage method perform the following.

AutoSeededRandomPool prng;
HexEncoder encoder(new FileSink(std::cout));

std::string message = "Yoda said, Do or do not. There is no try.";
std::string signature;

// Determine maximum signature size
size_t siglen = signer.MaxSignatureLength();
signature.resize(siglen);

// Sign, and trim signature to actual size
siglen = signer.SignMessage(prng, (const byte*)&message[0], message.size(), (byte*)&signature[0]);
signature.resize(siglen);

// Print signature to stdout
std::cout << "Signature: ";
StringSource(signature, true, new Redirector(encoder));
std::cout << "\n" << std::endl;

Running the program produces the following.

$ ./test.exe
Signature: B8EABDAA754BBCDC0B11ADE1FBA52CE39CD52FF42DE95E44CA6103652171468B63446
81DFB09F0D556EBF01BE43064D90C76711D9E1FF0FD3C41AF843DF17909

To sign a message using a pipeline perform the following.

AutoSeededRandomPool prng;
HexEncoder encoder(new FileSink(std::cout));

std::string message = "Yoda said, Do or do not. There is no try.";
std::string signature;

StringSource(message, true, new SignerFilter(prng, signer, new StringSink(signature)));

std::cout << "Signature: ";
StringSource(signature, true, new Redirector(encoder));
std::cout << "\n" << std::endl;

Running the program produces the following. Notice the signature is the same because ed25519 is a deterministic signature scheme.

$ ./test.exe
Signature: B8EABDAA754BBCDC0B11ADE1FBA52CE39CD52FF42DE95E44CA6103652171468B63446
81DFB09F0D556EBF01BE43064D90C76711D9E1FF0FD3C41AF843DF17909

Since the scheme is deterministic you can use NullRNG rather than a real PRNG:

StringSource(message, true, new SignerFilter(NullRNG(), signer, new StringSink(signature)));

Also see SignerFilter for more details on the filter.

Message Verification

There are two ways to verify a message. First you can use the VerifyMessage member function. Second you can use a pipeline. Examples of both are shown below.

To verify a message using the VerifyMessage method perform the following.

valid = verifier.VerifyMessage((const byte*)&message[0], message.size(),
                               (const byte*)&signature[0], signature.size());

if (valid == false)
    throw std::runtime_error("Invalid signature over message");
std::cout << "Verified signature over message\n" << std::endl;

Running the program produces the expected result:

Signature: B8EABDAA754BBCDC0B11ADE1FBA52CE39CD52FF42DE95E44CA6103652171468B63446
81DFB09F0D556EBF01BE43064D90C76711D9E1FF0FD3C41AF843DF17909

Verified signature over message

To verify a message using a pipeline perform the following.

StringSource(signature+message, true, new SignatureVerificationFilter(verifier,
             new ArraySink((byte*)&valid, sizeof(valid))));

if (valid == false)
    throw std::runtime_error("Invalid signature over message");
std::cout << "Verified signature over message\n" << std::endl;

The pipeline example writes the result of verification to the variable valid. You can forgo writing the result and ask the SignatureVerificationFilter to throw an exception with the following code.

StringSource(signature+message, true, new SignatureVerificationFilter(verifier,
             NULLPTR, THROW_EXCPETION)));

Also see SignatureVerificationFilter for more details on the filter.

Large Files

If you need to process large files then ed25519 has two additional member functions. The member functions are unique to ed25519, and other signer and verifier objects do not have them. The first is SignStream and the second is VerifyStream. SignStream and VerifyStream take a std::istream instead of a memory buffer using {message, messageLength}.

Due to the ed25519 design the full stream is used in two different places during signing. That is, the stream is used, then rewound, then used again during signing. Be sure the std::istream derived class you are using allows you to seek on the stream.

The following shows you how to sign a large file like a 4.4 GB ISO. You must use the SignStream and VerifyStream member functions, and you cannot use a pipeline. Before you begin you can create a large file with the dd command, if needed.

$ dd if=/dev/urandom of=./data.bin bs=1024 count=1M
1048576+0 records in
1048576+0 records out
1073741824 bytes (1.1 GB, 1.0 GiB) copied, 11.6213 s, 92.4 MB/s

Then to sign data.bin perform the following.

std::ifstream stream("data.bin");
if (stream.is_open() == false)
    throw std::runtime_error("Failed to open stream");

// Determine maximum size, allocate a string with the maximum size
std::string signature;
size_t siglen = signer.MaxSignatureLength();
signature.resize(siglen);

// Sign, and trim signature to actual size
siglen = signer.SignStream(NullRNG(), stream, (byte*)&signature[0]);
signature.resize(siglen);

Finally to verify data.bin perform the following.

std::ifstream stream("data.bin");
if (stream.is_open() == false)
    throw std::runtime_error("Failed to open stream");

valid = verifier.VerifyStream(stream, (const byte*)&signature[0], signature.size());

if (valid == false)
    throw std::runtime_error("Invalid signature over file");
    
std::cout << "Verified signature over file\n" << std::endl;

Running the program using the test data results in output similar to the following.

$ ./test.exe
Signature: 138DA1B7F092041A38743E99118B2CCF252313A3A9E88C3C2C0903DC2CC6084C266C4
F206A6642E86FB521E02E63B7DA484EC8F4F5113DFB7B052D4B56250C0D

Verified signature over file

Local files and large messages are not a good fit for ed25519. Bernstein seems to miss the local file signing use case. He also feels protocols should be designed for small messages, like 128-byte or 1024-byte packets, and users should not have to buffer parts of a message. According to Bernstein, the fundamental reason for processing smaller packets is to get rid of forged data as quickly as possible. For more reading, see Authenticating every packet on the boring-crypto mailing list.

Large file support was added at Crypto++ 8.1. Also see Issue 796 and Commit 0ca4c41a9780.

Complete Example

Below is a complete example that loads the private and public keys, signs a message, and then verifies a message.

#include "cryptlib.h"
#include "xed25519.h"
#include "filters.h"
#include "files.h"
#include "osrng.h"
#include "hex.h"
 
#include <string>
#include <iostream>
#include <iomanip>
#include <exception>

#define USE_PIPELINE 1

int main (int argc, char* argv[])
{
    using namespace CryptoPP;

    AutoSeededRandomPool prng;
    HexEncoder encoder(new FileSink(std::cout));

    FileSource fs1("private.key.bin", true);
    ed25519::Signer signer;
    signer.AccessPrivateKey().Load(fs1);

    bool valid = signer.GetPrivateKey().Validate(prng, 3);
    if (valid == false)
        throw std::runtime_error("Invalid private key");

    FileSource fs2("public.key.bin", true);
    ed25519::Verifier verifier;
    verifier.AccessPublicKey().Load(fs2);

    valid = verifier.GetPublicKey().Validate(prng, 3);
    if (valid == false)
        throw std::runtime_error("Invalid private key");

    std::cout << "Keys are valid\n" << std::endl;
    
    ////////////////////////////////////////////////////////////
    
    std::string message = "Yoda said, Do or do not. There is no try.";
    std::string signature;

#if USE_PIPELINE
    StringSource(message, true, new SignerFilter(NullRNG(), signer, new StringSink(signature)));
#else
    // Determine maximum size, allocate a string with the maximum size
    size_t siglen = signer.MaxSignatureLength();
    signature.resize(siglen);

    // Sign, and trim signature to actual size
    siglen = signer.SignMessage(NullRNG(), (const byte*)&message[0], message.size(),
                                (byte*)&signature[0]);
    signature.resize(siglen);
#endif

    std::cout << "Signature: ";
    StringSource(signature, true, new Redirector(encoder));
    std::cout << "\n" << std::endl;

    ////////////////////////////////////////////////////////////

#if USE_PIPELINE
    StringSource(signature+message, true, new SignatureVerificationFilter(verifier,
                 new ArraySink((byte*)&valid, sizeof(valid))));
#else
    valid = verifier.VerifyMessage((const byte*)&message[0], message.size(),
                                   (const byte*)&signature[0], signature.size());
#endif

    if (valid == false)
        throw std::runtime_error("Invalid signature over message");
    
    std::cout << "Verified signature over message\n" << std::endl;

    return 0; 
}

Donna Functions

The Donna namespace provides the functions ed25519_publickey, ed25519_sign and ed25519_sign_open. ed25519_publickey creates a public key from a private key. ed25519_sign signs a message. ed25519_sign_open verifies a message. The functions are entry points into Andrew Moon's constant time ed25519-donna.

The header of interest is donna.h, and the source files of interest are donna_32.cpp, donna_64.cpp and donna_sse.cpp depending on the platform. The functions are shown below for completeness, but you should avoid using them. The Donna functions may change without warning.

NAMESPACE_BEGIN(CryptoPP)
NAMESPACE_BEGIN(Donna)

int
ed25519_publickey_CXX(byte publicKey[32], const byte secretKey[32])
{
    using namespace CryptoPP::Donna::Ed25519;

    bignum256modm a;
    ALIGN(16) ge25519 A;
    hash_512bits extsk;

    /* A = aB */
    ed25519_extsk(extsk, secretKey);
    expand256_modm(a, extsk, 32);
    ge25519_scalarmult_base_niels(&A, ge25519_niels_base_multiples, a);
    ge25519_pack(publicKey, &A);

    return 0;
}

int
ed25519_publickey(byte publicKey[32], const byte secretKey[32])
{
    return ed25519_publickey_CXX(publicKey, secretKey);
}

int
ed25519_sign_CXX(const byte *m, size_t mlen, const byte sk[32], const byte pk[32], byte RS[64])
{
    using namespace CryptoPP::Donna::Ed25519;

    bignum256modm r, S, a;
    ALIGN(16) ge25519 R;
    hash_512bits extsk, hashr, hram;

    ed25519_extsk(extsk, sk);

    /* r = H(aExt[32..64], m) */
    SHA512 hash;
    hash.Update(extsk + 32, 32);
    hash.Update(m, mlen);
    hash.Final(hashr);
    expand256_modm(r, hashr, 64);

    /* R = rB */
    ge25519_scalarmult_base_niels(&R, ge25519_niels_base_multiples, r);
    ge25519_pack(RS, &R);

    /* S = H(R,A,m).. */
    ed25519_hram(hram, RS, pk, m, mlen);
    expand256_modm(S, hram, 64);

    /* S = H(R,A,m)a */
    expand256_modm(a, extsk, 32);
    mul256_modm(S, S, a);

    /* S = (r + H(R,A,m)a) */
    add256_modm(S, S, r);

    /* S = (r + H(R,A,m)a) mod L */
    contract256_modm(RS + 32, S);

    return 0;
}

int
ed25519_sign(const byte* message, size_t messageLength, const byte secretKey[32],
             const byte publicKey[32], byte signature[64])
{
    return ed25519_sign_CXX(message, messageLength, secretKey, publicKey, signature);
}

int
ed25519_sign_open_CXX(const byte *m, size_t mlen, const byte pk[32], const byte RS[64])
{
    using namespace CryptoPP::Donna::Ed25519;

    ALIGN(16) ge25519 R, A;
    hash_512bits hash;
    bignum256modm hram, S;
    unsigned char checkR[32];

    if ((RS[63] & 224) || !ge25519_unpack_negative_vartime(&A, pk))
        return -1;

    /* hram = H(R,A,m) */
    ed25519_hram(hash, RS, pk, m, mlen);
    expand256_modm(hram, hash, 64);

    /* S */
    expand256_modm(S, RS + 32, 32);

    /* SB - H(R,A,m)A */
    ge25519_double_scalarmult_vartime(&R, &A, hram, S);
    ge25519_pack(checkR, &R);

    /* check that R = SB - H(R,A,m)A */
    return ed25519_verify(RS, checkR, 32) ? 0 : -1;
}

int
ed25519_sign_open(const byte *message, size_t messageLength, const byte publicKey[32], const byte signature[64])
{
    return ed25519_sign_open_CXX(message, messageLength, publicKey, signature);
}

NAMESPACE_END  // Donna
NAMESPACE_END  // CryptoPP

The Donna code is used similar to the following in the library source code. Most Donna functions return a useless value and can be ignored.

void ed25519PrivateKey::SecretToPublicKey(byte y[PUBLIC_KEYLENGTH], const byte x[SECRET_KEYLENGTH])
{
    int ret = Donna::ed25519_publickey(y, x);
    CRYPTOPP_UNUSED(ret);
}

Benchmarks

ed25519 performs anywhere from 20x to 30x faster than Certicom's secp256r1 and secp256k1 curves. It is possible to pull more performance out of ed25519 signatures, but you have to switch to one of the latest implementations.

Below are benchmarks from a Core-i5 6400 @ 2.7 GHz.

Algorithm Milliseconds/Operation Megacycles/Operation
ed25519 Signature 0.018 0.048
ed25519 Verification 0.049 0.132
ECDSA secp256r1 Signature 0.515 1.391
ECDSA secp256r1 Verification 1.805 4.874

Below are benchmarks from a LeMaker HiKey Cortex-A53 ARMv8 dev-board @ 1.2 GHz.

Algorithm Milliseconds/Operation Megacycles/Operation
ed25519 Signature 0.213 0.256
ed25519 Verification 0.541 0.649
ECDSA secp256r1 Signature 5.420 6.504
ECDSA secp256r1 Verification 16.393 19.672

Below are benchmarks from a CubieTruck Cortex-A7 ARMv7 dev-board @ 1.2 GHz.

Algorithm Milliseconds/Operation Megacycles/Operation
ed25519 Signature 0.456 0.547
ed25519 Verification 1.195 1.434
ECDSA secp256r1 Signature 7.067 8.481
ECDSA secp256r1 Verification 19.143 22.971

And the results below are from Windows 8 and Visual Studio 2017 on a Core-i5 3250 @ 2.5 GHz.

Algorithm Milliseconds/Operation Megacycles/Operation
ed25519 Signature 0.029 0.075
ed25519 Verification 0.078 0.202
ECDSA secp256r1 Signature 0.796 2.070
ECDSA secp256r1 Verification 2.639 6.860

Downloads

No downloads available.