Skip to content

Add ECDSA pubkey recovery usage example #1714

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ exhaustive_tests
precompute_ecmult_gen
precompute_ecmult
ctime_tests
recovery_example
ecdh_example
ecdsa_example
schnorr_example
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

#### Added
- Added usage example for ECDSA public key recovery.

## [0.7.0] - 2025-07-21

#### Added
Expand Down
11 changes: 11 additions & 0 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,17 @@ if BUILD_WINDOWS
ecdsa_example_LDFLAGS += -lbcrypt
endif
TESTS += ecdsa_example
if ENABLE_MODULE_RECOVERY
noinst_PROGRAMS += recovery_example
recovery_example_SOURCES = examples/recovery.c
recovery_example_CPPFLAGS = -I$(top_srcdir)/include -DSECP256K1_STATIC
recovery_example_LDADD = libsecp256k1.la
recovery_example_LDFLAGS = -static
if BUILD_WINDOWS
recovery_example_LDFLAGS += -lbcrypt
endif
TESTS += recovery_example
endif
if ENABLE_MODULE_ECDH
noinst_PROGRAMS += ecdh_example
ecdh_example_SOURCES = examples/ecdh.c
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ Usage examples
Usage examples can be found in the [examples](examples) directory. To compile them you need to configure with `--enable-examples`.
* [ECDSA example](examples/ecdsa.c)
* [Schnorr signatures example](examples/schnorr.c)
* [ECDSA public key recovery example](examples/recovery.c)
* [Deriving a shared secret (ECDH) example](examples/ecdh.c)
* [ElligatorSwift key exchange example](examples/ellswift.c)
* [MuSig2 Schnorr multi-signatures example](examples/musig.c)
Expand Down
4 changes: 4 additions & 0 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ endfunction()

add_example(ecdsa)

if(SECP256K1_ENABLE_MODULE_RECOVERY)
add_example(recovery)
endif()

if(SECP256K1_ENABLE_MODULE_ECDH)
add_example(ecdh)
endif()
Expand Down
2 changes: 1 addition & 1 deletion examples/ecdh.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ int main(void) {
return EXIT_FAILURE;
}
/* Randomizing the context is recommended to protect against side-channel
* leakage See `secp256k1_context_randomize` in secp256k1.h for more
* 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);
Expand Down
2 changes: 1 addition & 1 deletion examples/ecdsa.c
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ int main(void) {
return EXIT_FAILURE;
}
/* Randomizing the context is recommended to protect against side-channel
* leakage See `secp256k1_context_randomize` in secp256k1.h for more
* 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);
Expand Down
158 changes: 158 additions & 0 deletions examples/recovery.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*************************************************************************
* 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 *
*************************************************************************/

/** This file demonstrates how to use the recovery module to create a
* recoverable ECDSA signature and extract the corresponding
* public key from it.
*/

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>

#include <secp256k1.h>
#include <secp256k1_recovery.h>

#include "examples_util.h"

/* Use my_memcmp_var instead of memcmp.
*
* Normally, memcmp should be fine, but we use my_memcmp_var
* here to avoid a false positive from valgrind on macOS.
* TODO: remove this in the event the bug is fixed with valgrind in the future.
*/
static int my_memcmp_var(const void *s1, const void *s2, size_t n) {
const unsigned char *p1 = s1, *p2 = s2;
size_t i;

for (i = 0; i < n; i++) {
int diff = p1[i] - p2[i];
if (diff != 0) {
return diff;
}
}
return 0;
}

int main(void) {
unsigned char msg[32] = "this_could_be_the_hash_of_a_msg";
unsigned char seckey[32];
unsigned char randomize[32];
unsigned char recoverable_sig_ser[64];
unsigned char serialized_pubkey[33];
unsigned char serialized_recovered_pubkey[33];
size_t len;
int return_val, recovery_id;
secp256k1_pubkey pubkey, recovered_pubkey;
secp256k1_ecdsa_recoverable_signature recoverable_sig;
secp256k1_ecdsa_signature normal_sig;

/* Before we can call actual API functions, we need to create a "context". */
secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
if (!fill_random(randomize, sizeof(randomize))) {
printf("Failed to generate randomness\n");
return EXIT_FAILURE;
}
/* 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 (!fill_random(seckey, sizeof(seckey))) {
printf("Failed to generate randomness\n");
return EXIT_FAILURE;
}
/* Try to create a public key with a valid context. This only fails if the
* secret key is zero or out of range (greater than secp256k1's order). Note
* that the probability of this occurring is negligible with a properly
* functioning random number generator. */
if (!secp256k1_ec_pubkey_create(ctx, &pubkey, seckey)) {
printf("Generated secret key is invalid. This indicates an issue with the random number generator.\n");
return EXIT_FAILURE;
}

/* Serialize the public key. Should always return 1 for a valid public key. */
len = sizeof(serialized_pubkey);
return_val = secp256k1_ec_pubkey_serialize(ctx, serialized_pubkey, &len, &pubkey, SECP256K1_EC_COMPRESSED);
assert(return_val);

/*** Signing ***/

/* Signing with a valid context, verified secret key
* and the default nonce function should never fail. */
return_val = secp256k1_ecdsa_sign_recoverable(ctx, &recoverable_sig, msg, seckey, NULL, NULL);
assert(return_val);

/* Serialize in compact format (64 bytes + recovery id integer) */
return_val = secp256k1_ecdsa_recoverable_signature_serialize_compact(ctx,
recoverable_sig_ser, &recovery_id, &recoverable_sig);
assert(return_val);

/*** Public key recovery / verification ***/

/* Deserialize the recoverable signature. This will return 0 if the signature can't be parsed correctly. */
if (!secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, &recoverable_sig, recoverable_sig_ser, recovery_id)) {
printf("Failed parsing the recoverable signature\n");
return EXIT_FAILURE;
}

/* Recover the public key */
if (!secp256k1_ecdsa_recover(ctx, &recovered_pubkey, &recoverable_sig, msg)) {
printf("Public key recovery failed\n");
return EXIT_FAILURE;
}
len = sizeof(serialized_recovered_pubkey);
return_val = secp256k1_ec_pubkey_serialize(ctx, serialized_recovered_pubkey,
&len, &recovered_pubkey, SECP256K1_EC_COMPRESSED);
assert(return_val);

/* Successful recovery guarantees a correct signature, but we also do an explicit verification
do demonstrate how to convert a recoverable to a normal ECDSA signature */
return_val = secp256k1_ecdsa_recoverable_signature_convert(ctx, &normal_sig, &recoverable_sig);
assert(return_val);
/* A converted recoverable signature doesn't necessarily follow the low-s rule that is required
* to pass `secp256k1_ecdsa_verify`, so we have to normalize it first (note that in this specific
* example that's a no-op, as `secp256k1_ecdsa_sign_recoverable` always creates low-s signatures,
* but in general the verifier is a different entity and can't rely on that) */
secp256k1_ecdsa_signature_normalize(ctx, &normal_sig, &normal_sig);
if (!secp256k1_ecdsa_verify(ctx, &normal_sig, msg, &recovered_pubkey)) {
printf("Signature verification with converted recoverable signature failed\n");
return EXIT_FAILURE;
}

/* Actual public key and recovered public key should match */
return_val = my_memcmp_var(serialized_pubkey, serialized_recovered_pubkey, sizeof(serialized_pubkey));
assert(return_val == 0);

printf(" Secret Key: ");
print_hex(seckey, sizeof(seckey));
printf(" Public Key: ");
print_hex(serialized_pubkey, sizeof(serialized_pubkey));
printf(" Rec. signature: ");
print_hex(recoverable_sig_ser, sizeof(recoverable_sig_ser));
printf(" Recovery id: %d\n", recovery_id);
printf("Rec. public key: ");
print_hex(serialized_recovered_pubkey, sizeof(serialized_recovered_pubkey));

/* 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.
*
* Here we are preventing these writes from being optimized out, as any good compiler
* will remove any writes that aren't used. */
secure_erase(seckey, sizeof(seckey));

return EXIT_SUCCESS;
}
2 changes: 1 addition & 1 deletion examples/schnorr.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ int main(void) {
return EXIT_FAILURE;
}
/* Randomizing the context is recommended to protect against side-channel
* leakage See `secp256k1_context_randomize` in secp256k1.h for more
* 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);
Expand Down