-
Notifications
You must be signed in to change notification settings - Fork 209
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add an ECDSA signing and verifying example
Co-authored-by: Jonas Nick <[email protected]>
- Loading branch information
Showing
2 changed files
with
210 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
/************************************************************************* | ||
* Written in 2020-2022 by Elichai Turkel * | ||
* To the extent possible under law, the author(s) have dedicated all * | ||
* copyright and related and neighboring rights to the software in this * | ||
* file to the public domain worldwide. This software is distributed * | ||
* without any warranty. For the CC0 Public Domain Dedication, see * | ||
* EXAMPLES_COPYING or https://creativecommons.org/publicdomain/zero/1.0 * | ||
*************************************************************************/ | ||
|
||
#include <stdio.h> | ||
#include <assert.h> | ||
#include <string.h> | ||
|
||
#include <secp256k1.h> | ||
|
||
#include "random.h" | ||
|
||
|
||
|
||
int main(void) { | ||
/* Instead of signing the message directly, we must sign a 32-byte hash. | ||
* Here the message is "Hello, world!" and the hash function was SHA-256. | ||
* An actual implementation should just call SHA-256, but this example | ||
* hardcodes the output to avoid depending on an additional library. | ||
* See https://bitcoin.stackexchange.com/questions/81115/if-someone-wanted-to-pretend-to-be-satoshi-by-posting-a-fake-signature-to-defrau/81116#81116 */ | ||
unsigned char msg_hash[32] = { | ||
0x31, 0x5F, 0x5B, 0xDB, 0x76, 0xD0, 0x78, 0xC4, | ||
0x3B, 0x8A, 0xC0, 0x06, 0x4E, 0x4A, 0x01, 0x64, | ||
0x61, 0x2B, 0x1F, 0xCE, 0x77, 0xC8, 0x69, 0x34, | ||
0x5B, 0xFC, 0x94, 0xC7, 0x58, 0x94, 0xED, 0xD3, | ||
}; | ||
unsigned char seckey[32]; | ||
unsigned char randomize[32]; | ||
unsigned char compressed_pubkey[33]; | ||
unsigned char serialized_signature[64]; | ||
size_t len; | ||
int is_signature_valid; | ||
int return_val; | ||
secp256k1_pubkey pubkey; | ||
secp256k1_ecdsa_signature sig; | ||
/* The specification in secp256k1.h states that `secp256k1_ec_pubkey_create` needs | ||
* a context object initialized for signing and `secp256k1_ecdsa_verify` needs | ||
* a context initialized for verification, which is why we create a context | ||
* for both signing and verification with the SECP256K1_CONTEXT_SIGN and | ||
* SECP256K1_CONTEXT_VERIFY flags. */ | ||
secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); | ||
if (!fill_random(randomize, sizeof(randomize))) { | ||
printf("Failed to generate randomness\n"); | ||
return 1; | ||
} | ||
/* Randomizing the context is recommended to protect against side-channel | ||
* leakage See `secp256k1_context_randomize` in secp256k1.h for more | ||
* information about it. This should never fail. */ | ||
return_val = secp256k1_context_randomize(ctx, randomize); | ||
assert(return_val); | ||
|
||
/*** Key Generation ***/ | ||
|
||
/* If the secret key is zero or out of range (bigger than secp256k1's | ||
* order), we try to sample a new key. Note that the probability of this | ||
* happening is negligible. */ | ||
while (1) { | ||
if (!fill_random(seckey, sizeof(seckey))) { | ||
printf("Failed to generate randomness\n"); | ||
return 1; | ||
} | ||
if (secp256k1_ec_seckey_verify(ctx, seckey)) { | ||
break; | ||
} | ||
} | ||
|
||
/* Public key creation using a valid context with a verified secret key should never fail */ | ||
return_val = secp256k1_ec_pubkey_create(ctx, &pubkey, seckey); | ||
assert(return_val); | ||
|
||
/* Serialize the pubkey in a compressed form(33 bytes). Should always return 1. */ | ||
len = sizeof(compressed_pubkey); | ||
return_val = secp256k1_ec_pubkey_serialize(ctx, compressed_pubkey, &len, &pubkey, SECP256K1_EC_COMPRESSED); | ||
assert(return_val); | ||
/* Should be the same size as the size of the output, because we passed a 33 byte array. */ | ||
assert(len == sizeof(compressed_pubkey)); | ||
|
||
/*** Signing ***/ | ||
|
||
/* Generate an ECDSA signature `noncefp` and `ndata` allows you to pass a | ||
* custom nonce function, passing `NULL` will use the RFC-6979 safe default. | ||
* Signing with a valid context, verified secret key | ||
* and the default nonce function should never fail. */ | ||
return_val = secp256k1_ecdsa_sign(ctx, &sig, msg_hash, seckey, NULL, NULL); | ||
assert(return_val); | ||
|
||
/* Serialize the signature in a compact form. Should always return 1 | ||
* according to the documentation in secp256k1.h. */ | ||
return_val = secp256k1_ecdsa_signature_serialize_compact(ctx, serialized_signature, &sig); | ||
assert(return_val); | ||
|
||
|
||
/*** Verification ***/ | ||
|
||
/* Deserialize the signature. This will return 0 if the signature can't be parsed correctly. */ | ||
if (!secp256k1_ecdsa_signature_parse_compact(ctx, &sig, serialized_signature)) { | ||
printf("Failed parsing the signature\n"); | ||
return 1; | ||
} | ||
|
||
/* Deserialize the public key. This will return 0 if the public key can't be parsed correctly. */ | ||
if (!secp256k1_ec_pubkey_parse(ctx, &pubkey, compressed_pubkey, sizeof(compressed_pubkey))) { | ||
printf("Failed parsing the public key\n"); | ||
return 1; | ||
} | ||
|
||
/* Verify a signature. This will return 1 if it's valid and 0 if it's not. */ | ||
is_signature_valid = secp256k1_ecdsa_verify(ctx, &sig, msg_hash, &pubkey); | ||
|
||
printf("Is the signature valid? %s\n", is_signature_valid ? "true" : "false"); | ||
printf("Secret Key: "); | ||
print_hex(seckey, sizeof(seckey)); | ||
printf("Public Key: "); | ||
print_hex(compressed_pubkey, sizeof(compressed_pubkey)); | ||
printf("Signature: "); | ||
print_hex(serialized_signature, sizeof(serialized_signature)); | ||
|
||
|
||
/* This will clear everything from the context and free the memory */ | ||
secp256k1_context_destroy(ctx); | ||
|
||
/* It's best practice to try to clear secrets from memory after using them. | ||
* This is done because some bugs can allow an attacker to leak memory, for | ||
* example through "out of bounds" array access (see Heartbleed), Or the OS | ||
* swapping them to disk. Hence, we overwrite the secret key buffer with zeros. | ||
* | ||
* TODO: Prevent these writes from being optimized out, as any good compiler | ||
* will remove any writes that aren't used. */ | ||
memset(seckey, 0, sizeof(seckey)); | ||
|
||
return 0; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
/************************************************************************* | ||
* Copyright (c) 2020-2021 Elichai Turkel * | ||
* Distributed under the CC0 software license, see the accompanying file * | ||
* EXAMPLES_COPYING or https://creativecommons.org/publicdomain/zero/1.0 * | ||
*************************************************************************/ | ||
|
||
/* | ||
* This file is an attempt at collecting best practice methods for obtaining randomness with different operating systems. | ||
* It may be out-of-date. Consult the documentation of the operating system before considering to use the methods below. | ||
* | ||
* Platform randomness sources: | ||
* Linux -> `getrandom(2)`(`sys/random.h`), if not available `/dev/urandom` should be used. http://man7.org/linux/man-pages/man2/getrandom.2.html, https://linux.die.net/man/4/urandom | ||
* macOS -> `getentropy(2)`(`sys/random.h`), if not available `/dev/urandom` should be used. https://www.unix.com/man-page/mojave/2/getentropy, https://opensource.apple.com/source/xnu/xnu-517.12.7/bsd/man/man4/random.4.auto.html | ||
* FreeBSD -> `getrandom(2)`(`sys/random.h`), if not available `kern.arandom` should be used. https://www.freebsd.org/cgi/man.cgi?query=getrandom, https://www.freebsd.org/cgi/man.cgi?query=random&sektion=4 | ||
* OpenBSD -> `getentropy(2)`(`unistd.h`), if not available `/dev/urandom` should be used. https://man.openbsd.org/getentropy, https://man.openbsd.org/urandom | ||
* Windows -> `BCryptGenRandom`(`bcrypt.h`). https://docs.microsoft.com/en-us/windows/win32/api/bcrypt/nf-bcrypt-bcryptgenrandom | ||
*/ | ||
|
||
#if defined(_WIN32) | ||
#include <windows.h> | ||
#include <ntstatus.h> | ||
#include <bcrypt.h> | ||
#elif defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) | ||
#include <sys/random.h> | ||
#elif defined(__OpenBSD__) | ||
#include <unistd.h> | ||
#else | ||
#error "Couldn't identify the OS" | ||
#endif | ||
|
||
#include <stddef.h> | ||
#include <limits.h> | ||
#include <stdio.h> | ||
|
||
|
||
/* Returns 1 on success, and 0 on failure. */ | ||
static int fill_random(unsigned char* data, size_t size) { | ||
#if defined(_WIN32) | ||
NTSTATUS res = BCryptGenRandom(NULL, data, size, BCRYPT_USE_SYSTEM_PREFERRED_RNG); | ||
if (res != STATUS_SUCCESS || size > ULONG_MAX) { | ||
return 0; | ||
} else { | ||
return 1; | ||
} | ||
#elif defined(__linux__) || defined(__FreeBSD__) | ||
/* If `getrandom(2)` is not available you should fallback to /dev/urandom */ | ||
ssize_t res = getrandom(data, size, 0); | ||
if (res < 0 || (size_t)res != size ) { | ||
return 0; | ||
} else { | ||
return 1; | ||
} | ||
#elif defined(__APPLE__) || defined(__OpenBSD__) | ||
/* If `getentropy(2)` is not available you should fallback to either | ||
* `SecRandomCopyBytes` or /dev/urandom */ | ||
int res = getentropy(data, size); | ||
if (res == 0) { | ||
return 1; | ||
} else { | ||
return 0; | ||
} | ||
#endif | ||
return 0; | ||
} | ||
|
||
static void print_hex(unsigned char* data, size_t size) { | ||
size_t i; | ||
printf("0x"); | ||
for (i = 0; i < size; i++) { | ||
printf("%02x", data[i]); | ||
} | ||
printf("\n"); | ||
} |