Elliptic Curve Digital Signature Algorithm

From Crypto++ Wiki
Jump to navigation Jump to search
ECDSA
Documentation
#include <cryptopp/eccrypto.h>

Elliptic Curve Digital Signature Algorithm, or ECDSA, is one of three digital signature schemes specified in FIPS-186. The current revision is Change 4, dated July 2013. If interested in the non-elliptic curve variant, see Digital Signature Algorithm.

Before operations such as key generation, signing, and verification can occur, we must chose a field and suitable domain parameters. For demonstration purposes, this example will use [math]\displaystyle{ F_p }[/math] as the field. This means one template argument to ECDSA will include ECP. If we were using [math]\displaystyle{ F_{2^m} }[/math], the template argument would be EC2N.

Crypto++ supplies a set of standard curves approved by ANSI, Brainpool, and NIST. Crypto++ does not provide curve generation functionality. If you need a custom curve, see Elliptic Curve Builder. Also see the Elliptic Curve Cryptography wiki page.

DSAConvertSignatureFormat shows you how to convert between Crypto++'s P1363 format and OpenSSL's ASN.1/DER signature format. It is used in the OpenSSL and Java example below.

Ján Jančár showed Crypto++ 8.2 and below leaked timing information in elliptic curve gear. You should upgrade to Crypto++ 8.3 and above. Also see Issue 869, Elliptic Curve timing leaks.

Crypto++ Validation

Crypto++'s performs ECDSA validation in ValidateECDSA from valdat2.cpp. Both [math]\displaystyle{ F_p }[/math] and [math]\displaystyle{ F_{2^m} }[/math] are demonstrated.

Choice of Fields

ECDSA is defined over both prime fields [math]\displaystyle{ F_p }[/math] and binary fields [math]\displaystyle{ F_{2^m} }[/math]. To operate over a prime field, use ECP:

ECDSA<ECP, SHA256>::Signer signer;
ECDSA<ECP, SHA256>::Verifier verifier;

For binary fields, specify EC2N:

ECDSA<EC2N, SHA256>::Signer signer;
ECDSA<EC2N, SHA256>::Verifier verifier;

Crypto++ Validation

Crypto++'s performs ECDSA validation in ValidateECDSA from valdat2.cpp. Both [math]\displaystyle{ F_p }[/math] and [math]\displaystyle{ F_{2^m} }[/math] are demonstrated.

Choice of Fields

ECDSA is defined over both prime fields [math]\displaystyle{ F_p }[/math] and binary fields [math]\displaystyle{ F_{2^m} }[/math]. To operate over a prime field, use ECP:

ECDSA<ECP, SHA256>::Signer signer;
ECDSA<ECP, SHA256>::Verifier verifier;

For binary fields, specify EC2N:

ECDSA<EC2N, SHA256>::Signer signer;
ECDSA<EC2N, SHA256>::Verifier verifier;

Key Generation

The following discusses public and private key generation.

Private Key

To generate a private key for signing, perform the following. Note that Initialize functions which take a RandomNumberGenerator will generate a private key.

AutoSeededRandomPool prng;
ECDSA<ECP, SHA256>::PrivateKey privateKey;

privateKey.Initialize( prng, ASN1::secp256k1() );
bool result = privateKey.Validate( prng, 3 );
if( !result ) { ... }

An alternative to using a random number generator and OID is shown below. In the code below, the OID is used to construct an object for domain parameters.

ECDSA<ECP, SHA256>::PrivateKey privateKey;
DL_GroupParameters_EC<ECP> params(ASN1::secp256k1());

privateKey.Initialize(prng, params);

Once the private key has been generated, the private exponent can be retrieved as follows.

const Integer& x = privateKey.GetPrivateExponent()

Calling GenerateRandom as shown below will not work as expected - it will cause a NotImplemented exception with the message, DL_GroupParameters_EC<EC>: curve generation is not implemented yet.

AutoSeededRandomPool prng;

ECDSA<ECP, SHA256>::PrivateKey privateKey;
privateKey.GenerateRandom(prng, g_nullNameValuePairs);

Be careful when working with ECDSA's parameterized arguments. The following will save and load properly, despite the difference between SHA1 and SHA256. When a key is saved or loaded, it is done so in PKCS #8 (private key) or X509 format (public key). The key formats are ignorant to the objects which use them (such as ECDSA).

ECDSA<ECP, SHA1>::PrivateKey k1;
k1.Initialize( prng, ASN1::secp256k1() );

const Integer& x1 = k1.GetPrivateExponent();
std::cout << "K1: " << std::hex << x1 << std::endl;

ByteQueue queue;
k1.Save(queue);

ECDSA<ECP, SHA256>::PrivateKey k2;
k2.Load(queue);

const Integer& x2 = k2.GetPrivateExponent();
std::cout << "K2: " << std::hex << x2 << std::endl;
$ ./test.exe
K1: 1058a0c19e4cc4cc669f880efc53163b8ecb62af43f0c276533022f53e2fae5h
K2: 1058a0c19e4cc4cc669f880efc53163b8ecb62af43f0c276533022f53e2fae5h

Signer

To generate a private key for signing using a Signer, perform the following.

AutoSeededRandomPool prng;
ECDSA<ECP, SHA256>::Signer signer;

signer.AccessKey().Initialize( prng, ASN1::secp256k1() );
bool result = signer.AccessKey().Validate( prng, 3 );
if( !result ) { ... }

We can also use a PrivateKey to initialize a signer:

ECDSA<ECP, SHA256>::PrivateKey privateKey
...
ECDSA<ECP, SHA256>::Signer signer(privateKey);

bool result = signer.AccessKey().Validate( prng, 3 );
if( !result ) { ... }

Public Key

To derive a public key from the private key, perform the following.

privateKey.MakePublicKey( publicKey );

bool result = publicKey.Validate( prng, 3 );
if( !result ) { ... }

Once the public key has been derived, the public element can be retrieved as follows.

const ECP::Point& q = publicKey.GetPublicElement()

const Integer& qx = q.x;
const Integer& qy = q.y;

Verifier

To use a high level verifier, perform the following.

ECDSA<ECP, SHA256>::PublicKey verifier( publicKey );
signer.AccessKey().Verify( prng, 3 );

Though unlikely, we could perform the following if the signer object were handy (the same signer and verifier usually do not exist in the same program).

ECDSA<ECP, SHA256>::PrivateKey privateKey = ...;
ECDSA<ECP, SHA256>::Signer signer( privateKey );
...

ECDSA<ECP, SHA256>::PublicKey verifier( signer );
bool result = verifier.AccessKey().Verify( prng, 3 );
if(!result) { ... }

Key Initialization

The following demonstrates public and private key initialization using previously generated parameters.

Private Key

Given a curve and the private exponent, perform the following to initialize the private key.

ECDSA<ECP, SHA256>::PrivateKey privateKey;
const Integer x = ...;       // private exponent

privateKey.Initialize( ASN1::secp256k1(), x );

bool result = privateKey.Validate( prng, 3 );
if( !result ) { ... }

In the example below, the private key is a hex encoded integer.

string exp = "E4A6CFB431471CFCAE491FD566D19C87082CF9FA7722D7FA24B2B3F5669DBEFB";

HexDecoder decoder;
decoder.Put((byte*)&exp[0], exp.size());
decoder.MessageEnd();

Integer x;
x.Decode(decoder, decoder.MaxRetrievable());

privateKey.Initialize(ASN1::secp256r1(), x);

Public Key

Similar to a private key, a public key is initialized as follows using the public element.

ECDSA<ECP, SHA256>::PublicKey publicKey;
const ECP::Point q = ...;    // public element

publicKey.Initialize( ASN1::secp256k1(), q );

bool result = publicKey.Validate( prng, 3 );
if( !result ) { ... }

In the example below, the public point is a simple concatenation of the x and y parts.

string pt = "2DB45A3F21889438B42C8F464C75292BACF5FDDB5DA0B492501B299CBFE92D8F"
            "DB90FC8FF4026129838B1BCAD1402CAE47FE7D8084E409A41AFCE16D63579C5F";

HexDecoder decoder;
decoder.Put((byte*)&pt[0], pt.size());
decoder.MessageEnd();

ECP::Point q;
size_t len = decoder.MaxRetrievable();

q.identity = false;
q.x.Decode(decoder, len/2);
q.y.Decode(decoder, len/2);

ECDSA<ECP, SHA256>::PublicKey publicKey;
publicKey.Initialize( ASN1::secp256r1(), q );

Key Persistence

The following uses Load and Save to read and write a PKCS #8 private or X509 public keys. For a complete discussion of PKCS #8 private keys and X509 public keys, see Keys and Formats.

Private Key

Given a public key and private key, we perform the following to save a private key to disk.

// Save private key in PKCS #8 format
FileSink fs( "private.ec.der", true /*binary*/ );
privateKey.Save( fs );

To load the private key from disk, perform the following:

// Load private key in PKCS #8 format
FileSource( "private.ec.der", true /*pump all*/ );
privateKey.Load( fs );

bool result = privateKey.Validate( prng, 3 );
if( !result ) { ... }

Signer

If working with a higher level ECDSA<ECP, SHA256>::Signer, use AccessKey to access the private key and call Load or Save on the returned object.

ECDSA<ECP, SHA256>::Signer signer( privateKey );

ByteQueue queue;
signer.AccessKey().Save( queue );

A ByteQueue is convenient for transferring a private key in memory. To save to disk, we can use a FileSink.

FileSink fs( "private.ec.der", true /*binary*/ );
signer.AccessKey().Save( fs );

Saving the key to disk using an ASCII encoding is shown below.

HexEncoder encoder( new FileSink( "private.ec.der", false /*binary*/ ));
signer.AccessKey().Save( encoder );

Public Key

Public keys are persisted similar to private keys.

// Save public key in X.509 format
FileSink fs( "public.ec.der", true /*binary*/ );
publiceKey.Save( fs );
// Load public key in X.509 format
FileSource fs( "public.ec.der", true /*pump all*/ );
publiceKey.Load( fs );

bool result = publicKey.Validate( prng, 3 );
if( !result ) { ... }

Verifier

If working with a higher level ECDSA<ECP, SHA256>::Verfier, use AccessKey to access the public key and call Load or Save on the returned object.

ECDSA<ECP, SHA256>::Verifier verifier( publicKey );

ByteQueue queue;
verifier.AccessKey().Save( queue );

A ByteQueue is convenient for transferring a public key in memory. To save to disk, we can use a FileSink.

FileSink fs( "public.ec.der", true /*binary*/ );
verifier.AccessKey().Save( fs );

Saving the key to disk using an ASCII encoding is shown below.

HexEncoder encoder( new FileSink( "public.ec.der", false /*binary*/ ));
verifier.AccessKey().Save( encoder );

Signers and Verifiers

Key Initialization shows how to generate or load a key. Message Signing and Message Verification shows how to sign and verify a message using Signers and Verifiers. Below is an example of how you can move among PrivateKey, PublicKey, Signer and Verfier.

ECDSA<ECP, SHA256>::Signer signer;
ECDSA<ECP, SHA256>::Verifier verifier;

ECDSA<ECP, SHA256>::PrivateKey& sKey = signer.AccessKey();
sKey.Initialize(prng, ASN1::secp256k1());
ECDSA<ECP, SHA256>::PublicKey& pKey = verifier.AccessKey();
sKey.MakePublicKey(pKey);

Message Signing

Signing a string is as follows. Note that a PRNG is required because the Digital Signature Standard specifies a per-message random value.

ECDSA<ECP, SHA256>::PrivateKey privateKey;
privateKey.Load(...);
ECDSA<ECP,SHA256>::Signer signer( privateKey );

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

StringSource s( message, true /*pump all*/,
    new SignerFilter( prng,
        signer,
        new StringSink( signature )
    ) // SignerFilter
); // StringSource

The previous signing example used filters, while the example below uses traditional C-style function calls. After completion, signature will hold a properly sized binary string with the signature of the message.

ECDSA<ECP, SHA256>::Signer signer;
signer.AccessKey().Initialize( prng, ASN1::secp256k1() );

string message = "Do or do not. There is no try.";

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

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

Message Verification

ECDSA is a Signature Scheme with Appendix. This means that the original message must be presented to the verifier function. Verification is performed as follows.

By default, the filter uses flags equal to SIGNATURE_AT_BEGIN | PUT_RESULT, so a boolean value is placed in result. See the SignatureVerificationFilter page for details on the filter and the flags.

ECDSA<ECP, SHA256>::PublicKey publicKey;
publicKey.Load(...);
ECDSA<ECP,SHA256>::Verifier verifier(publicKey);

// Result of the verification process
bool result = false;

// Exactly what was signed in the previous step
string message = ...;
// Output from the signing operation in the previous step
string signature = ...;

StringSource ss( signature+message, true /*pump all*/,
    new SignatureVerificationFilter(
        verifier,
        new ArraySink( (byte*)&result, sizeof(result) )
    ) // SignatureVerificationFilter
);

// Verification failure?
if( !result ) {...}

If we wanted the SignatureVerificationFilter to throw on verification failure, the code is shown below. Note we should be prepared to catch a SignatureVerificationFailed exception.

static const int VERIFICATION_FLAGS = SIGNATURE_AT_BEGIN | THROW_EXCEPTION;
StringSource ss( signature+message, true /*pump all*/,
    new SignatureVerificationFilter(
        ECDSA<ECP,SHA256>::Verifier(publicKey),
        NULL, /* No need for attached filter */
        VERIFICATION_FLAGS
    ) // SignatureVerificationFilter
);

The previous verification example used filters, while the example below uses traditional C-style function calls. It builds upon the signing example above.

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

ECDSA<ECP, SHA256>::PublicKey publicKey;
publicKey.Load(...);
ECDSA<ECP, SHA256>::Verifier verifier(publicKey);

bool result = verifier.VerifyMessage( (const byte*)&message[0], message.size(), (const byte*)&signature[0], signature.size() );
if(result)
  std::cout << "Verified signature on message" << std::endl;
else
  cerr << "Failed to verify signature on message" << std::endl;

Complete Example

Below is a complete example of generating a private key, loading it into a signer, creating a public key from the private key, loading it into a verifier, and then signing and verifying a message.

#include <iostream>
#include "eccrypto.h"
#include "osrng.h"
#include "oids.h"

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

    ECDSA<ECP, SHA256>::PrivateKey k1;
    k1.Initialize( prng, ASN1::secp256r1() );
    ECDSA<ECP, SHA256>::Signer signer(k1);

    std::string message = "Do or do not. There is no try.";
    size_t siglen = signer.MaxSignatureLength();
    std::string signature(siglen, 0x00);

    siglen = signer.SignMessage( prng, (const byte*)&message[0], message.size(), (byte*)&signature[0] );
    signature.resize(siglen);

    ECDSA<ECP, SHA256>::PublicKey publicKey;
    k1.MakePublicKey(publicKey);
    ECDSA<ECP, SHA256>::Verifier verifier(publicKey);

    bool result = verifier.VerifyMessage( (const byte*)&message[0], message.size(), (const byte*)&signature[0], signature.size() );
    
    // Verification failure?
    if( !result ) {
        std::cout << "Failed to verify signature on message" << std::endl;
    } else {
        std::cout << "All good!" << std::endl;
    }
    
    return 0;
}

Compressed Point

Sometimes you have a compact or compressed point and you need to load a public key from it. The code below shows you how to do it for secp256r1 and the compressed point 937120662418500f3ad7c892b1db7e7c2d85ec48c74e99d64dcb7083082bb4f3. The compressed point is the x portion of the coordinate.

The trick used below is, prepend 03 to the compact point and then let the library parse it as usual using DecodePoint.

#include "cryptlib.h"
#include "eccrypto.h"
#include "ecp.h"
#include "hex.h"
#include "oids.h"
#include "osrng.h"
 
#include <string>
#include <iostream>
#include <iomanip>

int main()
{
    using namespace CryptoPP;
    
    ECDSA<ECP, SHA256>::PublicKey pubKey;
    pubKey.AccessGroupParameters().Initialize(ASN1::secp256r1());

    std::string compactPoint = "03" /* compressed */
        "937120662418500f3ad7c892b1db7e7c 2d85ec48c74e99d64dcb7083082bb4f3";
   
    StringSource ss (compactPoint, true, new CryptoPP::HexDecoder);
    ECP::Point point;
 
    pubKey.GetGroupParameters().GetCurve().DecodePoint (point, ss, ss.MaxRetrievable());
 
    std::cout << "Result after decompression X: " << std::hex << point.x << std::endl;
    std::cout << "Result after decompression Y: " << std::hex << point.y << std::endl;
 
    return 0;
}

Running the program results in the following.

$ ./test.exe
Result after decompression X: 937120662418500f3ad7c892b1db7e7c2d85ec48c74e99d64dcb7083082bb4f3h
Result after decompression Y: 303508b051c3113b66c6d70fb68f3010cbc46595dd8d8057e6d942de0da913a9h

After decoding the point you can set it in the public key using the following.

pubKey.SetPublicElement(point);
std::cout << "X: " << std::hex << pubKey.GetPublicElement().x << std::endl;
std::cout << "Y: " << std::hex << pubKey.GetPublicElement().y << std::endl;

Which results in:

$ ./test.exe
X: 937120662418500f3ad7c892b1db7e7c2d85ec48c74e99d64dcb7083082bb4f3h
Y: 303508b051c3113b66c6d70fb68f3010cbc46595dd8d8057e6d942de0da913a9h

Domain Parameters

At times, it may be useful to dump domain parameters. We can use a single function to print both the public key and private key by accepting DL_GroupParameters_EC<ECP> as a parameter to the function since (not suprisingly) it is common to both objects.

ECDSA<ECP, SHA256>::PublicKey publicKey;
publicKey.Load(...);

PrintDomainParameters( publicKey );
void PrintDomainParameters( const DL_GroupParameters_EC<ECP>& params )
{
    std::cout << "Modulus:" << std::endl;
    std::cout << " " << params.GetCurve().GetField().GetModulus() << std::endl;
    
    std::cout << "Coefficient A:" << std::endl;
    std::cout << " " << params.GetCurve().GetA() << std::endl;
    
    std::cout << "Coefficient B:" << std::endl;
    std::cout << " " << params.GetCurve().GetB() << std::endl;
    
    std::cout << "Base Point:" << std::endl;
    std::cout << " X: " << params.GetSubgroupGenerator().x << std::endl; 
    std::cout << " Y: " << params.GetSubgroupGenerator().y << std::endl;
    
    std::cout << "Subgroup Order:" << std::endl;
    std::cout << " " << params.GetSubgroupOrder() << std::endl;
    
    std::cout << "Cofactor:" << std::endl;
    std::cout << " " << params.GetCofactor() << std::endl;    
}

A typical output from a 160 bit curve is shown below.

Modulus:
 1461501637330902918203684832716283019653785059327
Coefficient A:
 1461501637330902918203684832716283019653785059324
Coefficient B:
 163235791306168110546604919403271579530548345413
Base Point
 X: 425826231723888350446541592701409065913635568770
 Y: 203520114162904107873991457957346892027982641970
Subgroup Order:
 1461501637330902918203687197606826779884643492439
Cofactor:
 1

Private Exponent:
 542053241409584231606641348297804563147909139481

Public Element:
 X: 1330662819286567003101256740359821157367793328918
 Y: 294709699265533759639226491877167235372762906422

Precomputed Hashes

Sometimes you may want to sign a precomputed hash. The code below allows you to sign a precomputed hash by copying the input to the hash function to the output of the hash function. Effectively it is an identity function. Also see Sign precomputed hash with ECDSA or DSA on Stack Overflow.

Generally speaking signing a precomputed hash is a bad idea, especially if you don't compute the hash yourself. You don't want to disgorge creating the message digest from applying the secret key. You should avoid doing it.

$ cat test.cxx
#include "cryptlib.h"
#include "secblock.h"
#include "eccrypto.h"
#include "osrng.h"
#include "oids.h"
#include "hex.h"

#include <iostream>
#include <string>

using namespace CryptoPP;

template <unsigned int HASH_SIZE = 32>
class IdentityHash : public HashTransformation
{
public:
    CRYPTOPP_CONSTANT(DIGESTSIZE = HASH_SIZE)
    static const char * StaticAlgorithmName()
    {
        return "IdentityHash";
    }

    IdentityHash() : m_digest(HASH_SIZE), m_idx(0) {}

    virtual unsigned int DigestSize() const
    {
        return DIGESTSIZE;
    }

    virtual void Update(const byte *input, size_t length)
    {
        size_t s = STDMIN(STDMIN<size_t>(DIGESTSIZE, length),
                                         DIGESTSIZE - m_idx);    
        if (s)
            ::memcpy(&m_digest[m_idx], input, s);
        m_idx += s;
    }

    virtual void TruncatedFinal(byte *digest, size_t digestSize)
    {
        ThrowIfInvalidTruncatedSize(digestSize);

        if (m_idx != DIGESTSIZE)
            throw Exception(Exception::OTHER_ERROR, "Input size must be " + IntToString(DIGESTSIZE));

        if (digest)
            ::memcpy(digest, m_digest, digestSize);

        m_idx = 0;
    }

private:
    SecByteBlock m_digest;
    size_t m_idx;
};

int main(int argc, char* argv[])
{
    AutoSeededRandomPool prng;

    ECDSA<ECP, IdentityHash<32> >::PrivateKey privateKey;
    privateKey.Initialize(prng, ASN1::secp256r1());

    std::string message;
    message.resize(IdentityHash<32>::DIGESTSIZE);
    ::memset(&message[0], 0xAA, message.size());

    ECDSA<ECP, IdentityHash<32> >::Signer signer(privateKey);
    std::string signature;

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

    std::cout << "Signature: " << signature << std::endl;

    return 0;
}

OpenSSL and Java

ECDSA sometimes causes confusion when interop'ing with other libraries like OpenSSL and Java. The problem usually reduces to OpenSSL and Java use an ASN.1/DER signature format, and Crypto++ uses a IEEE P1363 format. Also see Cryptographic Interoperability: Digital Signatures, ECDSA sign with OpenSSL, verify with Crypto++ and ECDSA sign with BouncyCastle and verify with Crypto++.

The easiest way to interop is have Crypto++ convert the ASN.1/DER signature to a P1363 signature. The function of interest is DSAConvertSignatureFormat, which converts between ASN.1/DER, P1363 and OpenPGP formats.

Test Program

Here is the test program. Refer to Test Setup below for the generation of the parameters.

#include "cryptlib.h"
#include "eccrypto.h"
#include "dsa.h"
#include "sha.h"
#include "hex.h"

#include <iostream>

using namespace CryptoPP;

int main(int argc, char* argv[])
{
    // Load DER encoded public key
    FileSource pubKey("secp256k1-pub.der", true /*binary*/);
    ECDSA<ECP, SHA256>::Verifier verifier(pubKey);

    // Java or OpenSSL created signature. It is ASN.1
    //   SEQUENCE ::= { r INTEGER, s INTEGER }.
    const byte derSignature[] = {
        0x30, 0x44, 0x02, 0x20, 0x08, 0x66, 0xc8, 0xf1,
        0x6f, 0x15, 0x00, 0x40, 0x8a, 0xe2, 0x1b, 0x40,
        0x56, 0x28, 0x9c, 0x17, 0x8b, 0xca, 0x64, 0x99,
        0x37, 0xdc, 0x35, 0xad, 0xad, 0x60, 0x18, 0x4d,
        0x63, 0xcf, 0x4a, 0x06, 0x02, 0x20, 0x78, 0x4c,
        0xb7, 0x0b, 0xa3, 0xff, 0x4f, 0xce, 0xd3, 0x01,
        0x27, 0x5c, 0x6c, 0xed, 0x06, 0xf0, 0xd7, 0x63,
        0x6d, 0xc6, 0xbe, 0x06, 0x59, 0xe8, 0xc3, 0xa5,
        0xce, 0x8a, 0xf1, 0xde, 0x01, 0xd5
    };

    // P1363 'r || s' concatenation. The size is 32+32 due to field
    // size for r and s in secp-256. It is not 20+20 due to SHA-1.
    // The size is available in verifier.SignatureLength().
    SecByteBlock signature(verifier.SignatureLength());
    DSAConvertSignatureFormat(signature, signature.size(), DSA_P1363, 
                          derSignature, sizeof(derSignature), DSA_DER);

    // Message "Attack at dawn!"
    const byte message[] = {
        0x41, 0x74, 0x74, 0x61, 0x63, 0x6b, 0x20, 0x61,
        0x74, 0x20, 0x64, 0x61, 0x77, 0x6e, 0x21, 0x0a
    };
        
    // Standard signature checking in Crypto++
    bool result = verifier.VerifyMessage(message, sizeof(message), signature, signature.size());
    if (result)
        std::cout << "Verified message" << std::endl;
    else
        std::cout << "Failed to verify message" << std::endl;

    return 0;
}

And here is the result of running the test program.

$ ./test.exe
Verified message

Test Setup

Here is the setup used for the sample code shown above.

$ cat test.txt | openssl dgst -ecdsa-with-SHA256 -sign secp256k1-key.pem -keyform DER > test.sig

The command uses and produces the following parameters.

$ cat test.txt
Attack at dawn!

$ hexdump -C test.txt
00000000  41 74 74 61 63 6b 20 61  74 20 64 61 77 6e 21 0a  |Attack at dawn!.|
00000010

# Create private key in PEM format
$ openssl ecparam -name secp256k1 -genkey -noout -out secp256k1-key.pem

$ cat secp256k1-key.pem
-----BEGIN EC PRIVATE KEY-----
MHQCAQEEIO0D5Rjmes/91Nb3dHY9dxmbM7gVfxmB2+OVuLmWMbGXoAcGBSuBBAAK
oUQDQgAEgVNEuirUNCEVdf7nLSBUgU1GXLrtIBeglIbK54s91HlWKOKjk4CkJ3/B
wGAfcYKa+DgJ2IUQSD15K1T/ghM9eQ==
-----END EC PRIVATE KEY-----

# Convert private key to ASN.1/DER format
$ openssl ec -in secp256k1-key.pem -inform PEM -out secp256k1-key.der -outform DER

$ dumpasn1 secp256k1-key.der
  0 116: SEQUENCE {
  2   1:   INTEGER 1
  5  32:   OCTET STRING
       :     ED 03 E5 18 E6 7A CF FD D4 D6 F7 74 76 3D 77 19
       :     9B 33 B8 15 7F 19 81 DB E3 95 B8 B9 96 31 B1 97
 39   7:   [0] {
 41   5:     OBJECT IDENTIFIER secp256k1 (1 3 132 0 10)
       :     }
 48  68:   [1] {
 50  66:     BIT STRING
       :       04 81 53 44 BA 2A D4 34 21 15 75 FE E7 2D 20 54
       :       81 4D 46 5C BA ED 20 17 A0 94 86 CA E7 8B 3D D4
       :       79 56 28 E2 A3 93 80 A4 27 7F C1 C0 60 1F 71 82
       :       9A F8 38 09 D8 85 10 48 3D 79 2B 54 FF 82 13 3D
       :       79
       :     }
       :   }

# Create public key from private key
$ openssl ec -in secp256k1-key.der -inform DER -pubout -out secp256k1-pub.der -outform DER

$ dumpasn1 secp256k1-pub.der
  0  86: SEQUENCE {
  2  16:   SEQUENCE {
  4   7:     OBJECT IDENTIFIER ecPublicKey (1 2 840 10045 2 1)
 13   5:     OBJECT IDENTIFIER secp256k1 (1 3 132 0 10)
       :     }
 20  66:   BIT STRING
       :     04 81 53 44 BA 2A D4 34 21 15 75 FE E7 2D 20 54
       :     81 4D 46 5C BA ED 20 17 A0 94 86 CA E7 8B 3D D4
       :     79 56 28 E2 A3 93 80 A4 27 7F C1 C0 60 1F 71 82
       :     9A F8 38 09 D8 85 10 48 3D 79 2B 54 FF 82 13 3D
       :     79
       :   }

# Sign the message using the private key
$ cat test.txt | openssl dgst -ecdsa-with-SHA256 -sign secp256k1-key.der -keyform DER > test.sig

# Dump the signature as hex
$ hexdump -C test.sig
00000000  30 44 02 20 08 66 c8 f1  6f 15 00 40 8a e2 1b 40  |0D. .f..o..@...@|
00000010  56 28 9c 17 8b ca 64 99  37 dc 35 ad ad 60 18 4d  |V(....d.7.5..t.M|
00000020  63 cf 4a 06 02 20 78 4c  b7 0b a3 ff 4f ce d3 01  |c.J.. xL....O...|
00000030  27 5c 6c ed 06 f0 d7 63  6d c6 be 06 59 e8 c3 a5  |'\l....cm...Y...|
00000040  ce 8a f1 de 01 d5                                 |......|
00000046

# Dump the signature as ASN.1/DER
$ dumpasn1 test.sig
  0  68: SEQUENCE {
  2  32:   INTEGER
       :     08 66 C8 F1 6F 15 00 40 8A E2 1B 40 56 28 9C 17
       :     8B CA 64 99 37 DC 35 AD AD 60 18 4D 63 CF 4A 06
 36  32:   INTEGER
       :     78 4C B7 0B A3 FF 4F CE D3 01 27 5C 6C ED 06 F0
       :     D7 63 6D C6 BE 06 59 E8 C3 A5 CE 8A F1 DE 01 D5
       :   }

Signature Sizes

The following table was compiled to offer a comparison of domain parameters and relative signature sizes. Crypto++ Curve specifies the programmatic name for the approved curve. The signature sizes are displayed in bytes.

The signature format is P1363 and the size is the r || s concatenation. Note the size of r and s depend on the field size, and not the size of the hash. Both r and s will each be padded on the left with 0's to ensure each is exactly the same size as the field. That also means the number of bytes in r || s is 2 * FieldSize().

Well Known Curve Signature Size
secp112r1 28
secp128r1 32
secp224r1 56
secp256r1 64
secp192r1 80
secp384r1 96
secp521r1 132

Downloads

ECDSA-Test.zip - Crypto++ ECDSA sample program using filters

ECDSA-Test-C.zip - Crypto++ ECDSA sample program using C Style API