Skip to content

Commit 0b3dfb7

Browse files
theStackjosibake
andcommitted
Add ECDSA pubkey recovery usage example
Co-authored-by: josibake <[email protected]>
1 parent 20e3b44 commit 0b3dfb7

File tree

6 files changed

+178
-0
lines changed

6 files changed

+178
-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: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
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+
/* Use my_memcmp_var instead of memcmp.
25+
*
26+
* Normally, memcmp should be fine, but we use my_memcmp_var
27+
* here to avoid a false positive from valgrind on macOS.
28+
* TODO: remove this in the event the bug is fixed with valgrind in the future.
29+
*/
30+
static int my_memcmp_var(const void *s1, const void *s2, size_t n) {
31+
const unsigned char *p1 = s1, *p2 = s2;
32+
size_t i;
33+
34+
for (i = 0; i < n; i++) {
35+
int diff = p1[i] - p2[i];
36+
if (diff != 0) {
37+
return diff;
38+
}
39+
}
40+
return 0;
41+
}
42+
43+
int main(void) {
44+
unsigned char msg[32] = "this_could_be_the_hash_of_a_msg";
45+
unsigned char seckey[32];
46+
unsigned char randomize[32];
47+
unsigned char recoverable_sig_ser[64];
48+
unsigned char serialized_pubkey[33];
49+
unsigned char serialized_recovered_pubkey[33];
50+
size_t len;
51+
int return_val, recovery_id;
52+
secp256k1_pubkey pubkey, recovered_pubkey;
53+
secp256k1_ecdsa_recoverable_signature recoverable_sig;
54+
secp256k1_ecdsa_signature normal_sig;
55+
56+
/* Before we can call actual API functions, we need to create a "context". */
57+
secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
58+
if (!fill_random(randomize, sizeof(randomize))) {
59+
printf("Failed to generate randomness\n");
60+
return EXIT_FAILURE;
61+
}
62+
/* Randomizing the context is recommended to protect against side-channel
63+
* leakage. See `secp256k1_context_randomize` in secp256k1.h for more
64+
* information about it. This should never fail. */
65+
return_val = secp256k1_context_randomize(ctx, randomize);
66+
assert(return_val);
67+
68+
/*** Key Generation ***/
69+
if (!fill_random(seckey, sizeof(seckey))) {
70+
printf("Failed to generate randomness\n");
71+
return EXIT_FAILURE;
72+
}
73+
/* Try to create a public key with a valid context. This only fails if the
74+
* secret key is zero or out of range (greater than secp256k1's order). Note
75+
* that the probability of this occurring is negligible with a properly
76+
* functioning random number generator. */
77+
if (!secp256k1_ec_pubkey_create(ctx, &pubkey, seckey)) {
78+
printf("Generated secret key is invalid. This indicates an issue with the random number generator.\n");
79+
return EXIT_FAILURE;
80+
}
81+
82+
/* Serialize the public key. Should always return 1 for a valid public key. */
83+
len = sizeof(serialized_pubkey);
84+
return_val = secp256k1_ec_pubkey_serialize(ctx, serialized_pubkey, &len, &pubkey, SECP256K1_EC_COMPRESSED);
85+
assert(return_val);
86+
87+
/*** Signing ***/
88+
89+
/* Signing with a valid context, verified secret key
90+
* and the default nonce function should never fail. */
91+
return_val = secp256k1_ecdsa_sign_recoverable(ctx, &recoverable_sig, msg, seckey, NULL, NULL);
92+
assert(return_val);
93+
94+
/* Serialize in compact format (64 bytes + recovery id integer) */
95+
return_val = secp256k1_ecdsa_recoverable_signature_serialize_compact(ctx,
96+
recoverable_sig_ser, &recovery_id, &recoverable_sig);
97+
assert(return_val);
98+
99+
/*** Public key recovery / verification ***/
100+
101+
/* Deserialize the recoverable signature. This will return 0 if the signature can't be parsed correctly. */
102+
if (!secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, &recoverable_sig, recoverable_sig_ser, recovery_id)) {
103+
printf("Failed parsing the recoverable signature\n");
104+
return EXIT_FAILURE;
105+
}
106+
107+
/* Recover the public key */
108+
if (!secp256k1_ecdsa_recover(ctx, &recovered_pubkey, &recoverable_sig, msg)) {
109+
printf("Public key recovery failed\n");
110+
return EXIT_FAILURE;
111+
}
112+
len = sizeof(serialized_recovered_pubkey);
113+
return_val = secp256k1_ec_pubkey_serialize(ctx, serialized_recovered_pubkey,
114+
&len, &recovered_pubkey, SECP256K1_EC_COMPRESSED);
115+
assert(return_val);
116+
117+
/* Successful recovery guarantees a correct signature, but we also do an explicit verification
118+
do demonstrate how to convert a recoverable to a normal ECDSA signature */
119+
return_val = secp256k1_ecdsa_recoverable_signature_convert(ctx, &normal_sig, &recoverable_sig);
120+
assert(return_val);
121+
/* A converted recoverable signature doesn't necessarily follow the low-s rule that is required
122+
* to pass `secp256k1_ecdsa_verify`, so we have to normalize it first (note that in this specific
123+
* example that's a no-op, as `secp256k1_ecdsa_sign_recoverable` always creates low-s signatures,
124+
* but in general the verifier is a different entity and can't rely on that) */
125+
secp256k1_ecdsa_signature_normalize(ctx, &normal_sig, &normal_sig);
126+
if (!secp256k1_ecdsa_verify(ctx, &normal_sig, msg, &recovered_pubkey)) {
127+
printf("Signature verification with converted recoverable signature failed\n");
128+
return EXIT_FAILURE;
129+
}
130+
131+
/* Actual public key and recovered public key should match */
132+
return_val = my_memcmp_var(serialized_pubkey, serialized_recovered_pubkey, sizeof(serialized_pubkey));
133+
assert(return_val == 0);
134+
135+
printf(" Secret Key: ");
136+
print_hex(seckey, sizeof(seckey));
137+
printf(" Public Key: ");
138+
print_hex(serialized_pubkey, sizeof(serialized_pubkey));
139+
printf(" Rec. signature: ");
140+
print_hex(recoverable_sig_ser, sizeof(recoverable_sig_ser));
141+
printf(" Recovery id: %d\n", recovery_id);
142+
printf("Rec. public key: ");
143+
print_hex(serialized_recovered_pubkey, sizeof(serialized_recovered_pubkey));
144+
145+
/* This will clear everything from the context and free the memory */
146+
secp256k1_context_destroy(ctx);
147+
148+
/* It's best practice to try to clear secrets from memory after using them.
149+
* This is done because some bugs can allow an attacker to leak memory, for
150+
* example through "out of bounds" array access (see Heartbleed), or the OS
151+
* swapping them to disk. Hence, we overwrite the secret key buffer with zeros.
152+
*
153+
* Here we are preventing these writes from being optimized out, as any good compiler
154+
* will remove any writes that aren't used. */
155+
secure_erase(seckey, sizeof(seckey));
156+
157+
return EXIT_SUCCESS;
158+
}

0 commit comments

Comments
 (0)