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: