Sign and Verify a Message with Openssl ECDSA Library

This article wants to show how to sign and verify a message using an Elliptic Curve Digital Signature Algorithm.

In particular, I am going to use secp256k1 class of curves used in Bitcoin.

To understand almost all the OpenSSL data structure you can read this quote from OpenSSL wiki:

An EC_GROUP structure is used to represent the definition of an elliptic curve. Points on a curve are stored using an EC_POINT structure. An EC_KEY is used to hold a private/public key pair, where a private key is simply a BIGNUM and a public key is a point on a curve (represented by an EC_POINT).

The code is:

    // filename: main.cpp
    #include <string.h>
    #include "openssl/ec.h"
    #include "openssl/ecdsa.h"
    #include "openssl/bn.h"
    #include "openssl/obj_mac.h"
    #include "openssl/sha.h"

    int main()
    {
        EC_KEY         *key_pair_obj = nullptr;
        int             ret_error;
        BIGNUM         *priv_key;
        EC_POINT       *pub_key;
        EC_GROUP       *secp256k1_group;
        char           *pub_key_char, *priv_key_char;

        const char     *message = "message to sign";
        unsigned char   buffer_digest[SHA256_DIGEST_LENGTH];
        uint8_t        *digest;
        uint8_t       *signature;
        uint32_t        signature_len;
        int             verification;

        BIGNUM          *bn;
        EC_KEY          *imported_key_pair = nullptr;
        EC_GROUP        *curve_group;
        EC_POINT        *public_point;
        int              char_read;

        // Generate secp256k1 key pair
        key_pair_obj = EC_KEY_new_by_curve_name(NID_secp256k1);
        ret_error    = EC_KEY_generate_key(key_pair_obj);

        // Get private key
        priv_key      = (BIGNUM *)EC_KEY_get0_private_key(key_pair_obj);
        priv_key_char = BN_bn2hex(priv_key);

        // Get public key
        pub_key         = (EC_POINT *)EC_KEY_get0_public_key(key_pair_obj);
        secp256k1_group = EC_GROUP_new_by_curve_name(NID_secp256k1);
        pub_key_char    = EC_POINT_point2hex(secp256k1_group, pub_key, POINT_CONVERSION_COMPRESSED, nullptr);
        EC_GROUP_free(secp256k1_group);

        printf("Pivate key: %s\n", priv_key_char);
        printf("Public key: %s\n", pub_key_char);

        // Sign message
        signature_len = ECDSA_size(key_pair_obj); // the signature size depends on the key
        signature     = (uint8_t *) OPENSSL_malloc(signature_len);
        digest        = SHA256((const unsigned char *)message, strlen(message), buffer_digest);
        ret_error     = ECDSA_sign(0, (const uint8_t *)digest, SHA256_DIGEST_LENGTH, signature, &signature_len, key_pair_obj);

        printf("Message SHA256: ");for (uint32_t i = 0; i < SHA256_DIGEST_LENGTH; i++) printf("%02x", digest   [i]); printf("\n");
        printf("Signature     : "); for(uint32_t i = 0; i < signature_len       ; i++) printf("%02x", signature[i]); printf("\n");

        // Verify the signature
        verification = ECDSA_verify(0, digest, SHA256_DIGEST_LENGTH, signature, signature_len, key_pair_obj);
        if (verification == 1)
            printf("Verification    successful\n");
        else
            printf("Verification    NOT successful\n");
        EC_KEY_free(key_pair_obj);

        // Double check process for correctness
        imported_key_pair = EC_KEY_new_by_curve_name(NID_secp256k1);
        curve_group       = EC_GROUP_new_by_curve_name(NID_secp256k1);
        public_point      = EC_POINT_new(curve_group);
        public_point      = EC_POINT_hex2point(curve_group, pub_key_char, public_point, nullptr);
        ret_error         = EC_KEY_set_public_key(imported_key_pair, public_point);
        EC_GROUP_free(curve_group);
        EC_POINT_free(public_point);
        free(pub_key_char);

        bn        = BN_new();
        char_read = BN_hex2bn(&bn, priv_key_char);
        ret_error = EC_KEY_set_private_key(imported_key_pair, bn);
        BN_clear_free(bn);
        free(priv_key_char);
        
        verification = ECDSA_verify(0, digest, SHA256_DIGEST_LENGTH, signature, signature_len, imported_key_pair);
        if (verification == 1)
            printf("Re-Verification successful\n");
        else
            printf("Re-Verification NOT successful\n");
        EC_KEY_free(imported_key_pair);

        OPENSSL_free(signature);

        return 0;
    }

Bear in mind that this code has no checks on the return values whatsoever.

In line 32, an EC_KEY curve object is created but it is empty at the moment.

In line 33, the private and public key parts are created.

In lines 36-37, the private key is extracted from the key object. The private key is represented as big-number (BIGNUM) thus it is encoded in hex values.

In lines 40-42, the public key is extracted from the key object. The public key is a EC_POINT on the curve thus to be extracted the function EC_POINT_point2hex needs to know which curve it is.

In lines 49-52, the SHA256 of the message is computed and the signature is computed after it. The signature length depends on the dimension of the key (line 47).

In line 58, the signature is verified.

To double check that the signature is correct, I am going to import the public and private key, as hexadecimal strings, into a key object and I am going to use it to verify the signature.

In line 66-70, I am importing the public key (the point of the curve) from its hexadecimal string representation.

In line 75-77, I am importing the private key (the random big-number) from its hexadecimal string representation.

In line 81, the signature is verified again.

To compile the code use:

    g++ -o main main.cpp -lcrypto

It is also good practice to check for memory leak with Valgrind. In order to use it, we need to compile the program with debug information and disable the compiler optimizations:

    g++ -g -O0 -o main main.cpp -lcrypto
    valgrind --leak-check=yes ./main

Here you can find the function prototypes along with the library where it is declared:

    #include "openssl/ec.h"      
    EC_KEY         *EC_KEY_new_by_curve_name  (int nid);
    int             EC_KEY_generate_key       (EC_KEY *key);
    const BIGNUM   *EC_KEY_get0_private_key   (const EC_KEY *key);
    const EC_POINT *EC_KEY_get0_public_key    (const EC_KEY *key);
    int             EC_KEY_set_private_key    (EC_KEY *key, const BIGNUM *prv);
    int             EC_KEY_set_public_key     (EC_KEY *key, const EC_POINT *pub);
    EC_GROUP       *EC_GROUP_new_by_curve_name(int nid);
    char           *EC_POINT_point2hex        (const EC_GROUP *group, const EC_POINT *p,
                                               point_conversion_form_t form, BN_CTX *ctx);
    EC_POINT       *EC_POINT_hex2point        (const EC_GROUP *group, const char *hex,
                                               EC_POINT *p, BN_CTX *ctx);
    void            EC_KEY_free               (EC_KEY *key);
    void            EC_GROUP_free             (EC_GROUP *group);
    void            EC_POINT_free             (EC_POINT *point);

    #include "openssl/ecdsa.h"
    int ECDSA_size  (const EC_KEY *eckey)
    int ECDSA_sign  (int type, const unsigned char *dgst, int dgstlen,
                     unsigned char *sig, unsigned int *siglen, EC_KEY *eckey);
                     "type" --> is ignored for now
    int ECDSA_verify(int type, const unsigned char *dgst, int dgstlen,
                     const unsigned char *sig, int siglen, EC_KEY *eckey);
                     "type" --> is ignored for now

    #include "openssl/bn.h"      
    char *BN_bn2hex    (const BIGNUM *a);
    int   BN_hex2bn    (BIGNUM **a, const char *str);
    void  BN_clear_free(BIGNUM *a);

    #include "openssl/obj_mac.h"
    NID_secp256k1

    #include "openssl/sha.h"
    SHA256_DIGEST_LENGTH
    unsigned char *SHA256(const unsigned char *d, size_t n, unsigned char *md);

More to read: