Skip to content

Commit 8b3cecf

Browse files
committed
Add ECDSA pubkey recovery usage example
1 parent 20e3b44 commit 8b3cecf

File tree

6 files changed

+154
-0
lines changed

6 files changed

+154
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ exhaustive_tests
77
precompute_ecmult_gen
88
precompute_ecmult
99
ctime_tests
10+
recovery_example
1011
ecdh_example
1112
ecdsa_example
1213
schnorr_example

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
#### Added
11+
- Added usage example for ECDSA public key recovery.
12+
1013
## [0.7.0] - 2025-07-21
1114

1215
#### Added

Makefile.am

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,17 @@ if BUILD_WINDOWS
163163
ecdsa_example_LDFLAGS += -lbcrypt
164164
endif
165165
TESTS += ecdsa_example
166+
if ENABLE_MODULE_RECOVERY
167+
noinst_PROGRAMS += recovery_example
168+
recovery_example_SOURCES = examples/recovery.c
169+
recovery_example_CPPFLAGS = -I$(top_srcdir)/include -DSECP256K1_STATIC
170+
recovery_example_LDADD = libsecp256k1.la
171+
recovery_example_LDFLAGS = -static
172+
if BUILD_WINDOWS
173+
recovery_example_LDFLAGS += -lbcrypt
174+
endif
175+
TESTS += recovery_example
176+
endif
166177
if ENABLE_MODULE_ECDH
167178
noinst_PROGRAMS += ecdh_example
168179
ecdh_example_SOURCES = examples/ecdh.c

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ Usage examples
149149
Usage examples can be found in the [examples](examples) directory. To compile them you need to configure with `--enable-examples`.
150150
* [ECDSA example](examples/ecdsa.c)
151151
* [Schnorr signatures example](examples/schnorr.c)
152+
* [ECDSA public key recovery example](examples/recovery.c)
152153
* [Deriving a shared secret (ECDH) example](examples/ecdh.c)
153154
* [ElligatorSwift key exchange example](examples/ellswift.c)
154155
* [MuSig2 Schnorr multi-signatures example](examples/musig.c)

examples/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ endfunction()
1414

1515
add_example(ecdsa)
1616

17+
if(SECP256K1_ENABLE_MODULE_RECOVERY)
18+
add_example(recovery)
19+
endif()
20+
1721
if(SECP256K1_ENABLE_MODULE_ECDH)
1822
add_example(ecdh)
1923
endif()

examples/recovery.c

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
/*************************************************************************
2+
* To the extent possible under law, the author(s) have dedicated all *
3+
* copyright and related and neighboring rights to the software in this *
4+
* file to the public domain worldwide. This software is distributed *
5+
* without any warranty. For the CC0 Public Domain Dedication, see *
6+
* EXAMPLES_COPYING or https://creativecommons.org/publicdomain/zero/1.0 *
7+
*************************************************************************/
8+
9+
/** This file demonstrates how to use the recovery module to create a
10+
* recoverable ECDSA signature and extract the corresponding
11+
* public key from it.
12+
*/
13+
14+
#include <stdio.h>
15+
#include <stdlib.h>
16+
#include <assert.h>
17+
#include <string.h>
18+
19+
#include <secp256k1.h>
20+
#include <secp256k1_recovery.h>
21+
22+
#include "examples_util.h"
23+
24+
int main(void) {
25+
unsigned char msg[32] = "this_could_be_the_hash_of_a_msg";
26+
unsigned char seckey[32];
27+
unsigned char randomize[32];
28+
unsigned char recoverable_sig_ser[64];
29+
unsigned char serialized_pubkey[33];
30+
unsigned char serialized_recovered_pubkey[33];
31+
size_t len;
32+
int return_val, recovery_id;
33+
secp256k1_pubkey pubkey, recovered_pubkey;
34+
secp256k1_ecdsa_recoverable_signature recoverable_sig;
35+
secp256k1_ecdsa_signature normal_sig;
36+
37+
/* Before we can call actual API functions, we need to create a "context". */
38+
secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
39+
if (!fill_random(randomize, sizeof(randomize))) {
40+
printf("Failed to generate randomness\n");
41+
return EXIT_FAILURE;
42+
}
43+
/* Randomizing the context is recommended to protect against side-channel
44+
* leakage. See `secp256k1_context_randomize` in secp256k1.h for more
45+
* information about it. This should never fail. */
46+
return_val = secp256k1_context_randomize(ctx, randomize);
47+
assert(return_val);
48+
49+
/*** Key Generation ***/
50+
if (!fill_random(seckey, sizeof(seckey))) {
51+
printf("Failed to generate randomness\n");
52+
return EXIT_FAILURE;
53+
}
54+
/* Try to create a public key with a valid context. This only fails if the
55+
* secret key is zero or out of range (greater than secp256k1's order). Note
56+
* that the probability of this occurring is negligible with a properly
57+
* functioning random number generator. */
58+
if (!secp256k1_ec_pubkey_create(ctx, &pubkey, seckey)) {
59+
printf("Generated secret key is invalid. This indicates an issue with the random number generator.\n");
60+
return EXIT_FAILURE;
61+
}
62+
63+
/* Serialize the public key. Should always return 1 for a valid public key. */
64+
len = sizeof(serialized_pubkey);
65+
return_val = secp256k1_ec_pubkey_serialize(ctx, serialized_pubkey, &len, &pubkey, SECP256K1_EC_COMPRESSED);
66+
assert(return_val);
67+
68+
/*** Signing ***/
69+
70+
/* Signing with a valid context, verified secret key
71+
* and the default nonce function should never fail. */
72+
return_val = secp256k1_ecdsa_sign_recoverable(ctx, &recoverable_sig, msg, seckey, NULL, NULL);
73+
assert(return_val);
74+
75+
/* Serialize in compact format (64 bytes + recovery id integer) */
76+
return_val = secp256k1_ecdsa_recoverable_signature_serialize_compact(ctx,
77+
recoverable_sig_ser, &recovery_id, &recoverable_sig);
78+
assert(return_val);
79+
80+
/*** Public key recovery / verification ***/
81+
82+
/* Deserialize the recoverable signature. This will return 0 if the signature can't be parsed correctly. */
83+
if (!secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, &recoverable_sig, recoverable_sig_ser, recovery_id)) {
84+
printf("Failed parsing the recoverable signature\n");
85+
return EXIT_FAILURE;
86+
}
87+
88+
/* Recover the public key */
89+
if (!secp256k1_ecdsa_recover(ctx, &recovered_pubkey, &recoverable_sig, msg)) {
90+
printf("Public key recovery failed\n");
91+
return EXIT_FAILURE;
92+
}
93+
len = sizeof(serialized_recovered_pubkey);
94+
return_val = secp256k1_ec_pubkey_serialize(ctx, serialized_recovered_pubkey,
95+
&len, &recovered_pubkey, SECP256K1_EC_COMPRESSED);
96+
assert(return_val);
97+
98+
/* Successful recovery guarantees a correct signature, but we also do an explicit verification
99+
do demonstrate how to convert a recoverable to a normal ECDSA signature */
100+
return_val = secp256k1_ecdsa_recoverable_signature_convert(ctx, &normal_sig, &recoverable_sig);
101+
assert(return_val);
102+
if (!secp256k1_ecdsa_verify(ctx, &normal_sig, msg, &recovered_pubkey)) {
103+
printf("Signature verification with converted recoverable signature failed\n");
104+
return EXIT_FAILURE;
105+
}
106+
107+
/* Actual public key and recovered public key should match */
108+
return_val = memcmp(serialized_pubkey, serialized_recovered_pubkey, sizeof(serialized_pubkey));
109+
assert(return_val == 0);
110+
111+
printf(" Secret Key: ");
112+
print_hex(seckey, sizeof(seckey));
113+
printf(" Public Key: ");
114+
print_hex(serialized_pubkey, sizeof(serialized_pubkey));
115+
printf(" Rec. signature: ");
116+
print_hex(recoverable_sig_ser, sizeof(recoverable_sig_ser));
117+
printf(" Recovery id: %d\n", recovery_id);
118+
printf("Rec. public key: ");
119+
print_hex(serialized_recovered_pubkey, sizeof(serialized_recovered_pubkey));
120+
121+
/* This will clear everything from the context and free the memory */
122+
secp256k1_context_destroy(ctx);
123+
124+
/* It's best practice to try to clear secrets from memory after using them.
125+
* This is done because some bugs can allow an attacker to leak memory, for
126+
* example through "out of bounds" array access (see Heartbleed), or the OS
127+
* swapping them to disk. Hence, we overwrite the secret key buffer with zeros.
128+
*
129+
* Here we are preventing these writes from being optimized out, as any good compiler
130+
* will remove any writes that aren't used. */
131+
secure_erase(seckey, sizeof(seckey));
132+
133+
return EXIT_SUCCESS;
134+
}

0 commit comments

Comments
 (0)