Skip to content

Commit 202a030

Browse files
committed
Merge bitcoin#850: add secp256k1_ec_pubkey_cmp method
6eceec6 add `secp256k1_xonly_pubkey_cmp` method (Andrew Poelstra) 0d9561a add `secp256k1_ec_pubkey_cmp` method (Andrew Poelstra) Pull request description: ACKs for top commit: elichai: Code review ACK 6eceec6 jonasnick: ACK 6eceec6 real-or-random: ACK 6eceec6 Tree-SHA512: f95cbf65f16c88a4adfa1ea7cc6ddabab14baa3b68fa069e78e6faad4852cdbfaea42ee72590d2e0b8f3159cf9b37969511550eb6b2d256b101e2147711cc817
2 parents 1e78c18 + 6eceec6 commit 202a030

File tree

6 files changed

+176
-5
lines changed

6 files changed

+176
-5
lines changed

include/secp256k1.h

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,9 @@ typedef struct secp256k1_scratch_space_struct secp256k1_scratch_space;
6363
* The exact representation of data inside is implementation defined and not
6464
* guaranteed to be portable between different platforms or versions. It is
6565
* however guaranteed to be 64 bytes in size, and can be safely copied/moved.
66-
* If you need to convert to a format suitable for storage, transmission, or
67-
* comparison, use secp256k1_ec_pubkey_serialize and secp256k1_ec_pubkey_parse.
66+
* If you need to convert to a format suitable for storage or transmission,
67+
* use secp256k1_ec_pubkey_serialize and secp256k1_ec_pubkey_parse. To
68+
* compare keys, use secp256k1_ec_pubkey_cmp.
6869
*/
6970
typedef struct {
7071
unsigned char data[64];
@@ -383,6 +384,21 @@ SECP256K1_API int secp256k1_ec_pubkey_serialize(
383384
unsigned int flags
384385
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
385386

387+
/** Compare two public keys using lexicographic (of compressed serialization) order
388+
*
389+
* Returns: <0 if the first public key is less than the second
390+
* >0 if the first public key is greater than the second
391+
* 0 if the two public keys are equal
392+
* Args: ctx: a secp256k1 context object.
393+
* In: pubkey1: first public key to compare
394+
* pubkey2: second public key to compare
395+
*/
396+
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_pubkey_cmp(
397+
const secp256k1_context* ctx,
398+
const secp256k1_pubkey* pubkey1,
399+
const secp256k1_pubkey* pubkey2
400+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
401+
386402
/** Parse an ECDSA signature in compact (64 bytes) format.
387403
*
388404
* Returns: 1 when the signature could be parsed, 0 otherwise.

include/secp256k1_extrakeys.h

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ extern "C" {
1515
* The exact representation of data inside is implementation defined and not
1616
* guaranteed to be portable between different platforms or versions. It is
1717
* however guaranteed to be 64 bytes in size, and can be safely copied/moved.
18-
* If you need to convert to a format suitable for storage, transmission, or
19-
* comparison, use secp256k1_xonly_pubkey_serialize and
20-
* secp256k1_xonly_pubkey_parse.
18+
* If you need to convert to a format suitable for storage, transmission, use
19+
* use secp256k1_xonly_pubkey_serialize and secp256k1_xonly_pubkey_parse. To
20+
* compare keys, use secp256k1_xonly_pubkey_cmp.
2121
*/
2222
typedef struct {
2323
unsigned char data[64];
@@ -67,6 +67,21 @@ SECP256K1_API int secp256k1_xonly_pubkey_serialize(
6767
const secp256k1_xonly_pubkey* pubkey
6868
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
6969

70+
/** Compare two x-only public keys using lexicographic order
71+
*
72+
* Returns: <0 if the first public key is less than the second
73+
* >0 if the first public key is greater than the second
74+
* 0 if the two public keys are equal
75+
* Args: ctx: a secp256k1 context object.
76+
* In: pubkey1: first public key to compare
77+
* pubkey2: second public key to compare
78+
*/
79+
SECP256K1_API int secp256k1_xonly_pubkey_cmp(
80+
const secp256k1_context* ctx,
81+
const secp256k1_xonly_pubkey* pk1,
82+
const secp256k1_xonly_pubkey* pk2
83+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
84+
7085
/** Converts a secp256k1_pubkey into a secp256k1_xonly_pubkey.
7186
*
7287
* Returns: 1 if the public key was successfully converted

src/modules/extrakeys/main_impl.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,32 @@ int secp256k1_xonly_pubkey_serialize(const secp256k1_context* ctx, unsigned char
5555
return 1;
5656
}
5757

58+
int secp256k1_xonly_pubkey_cmp(const secp256k1_context* ctx, const secp256k1_xonly_pubkey* pk0, const secp256k1_xonly_pubkey* pk1) {
59+
unsigned char out[2][32];
60+
const secp256k1_xonly_pubkey* pk[2];
61+
int i;
62+
63+
VERIFY_CHECK(ctx != NULL);
64+
pk[0] = pk0; pk[1] = pk1;
65+
for (i = 0; i < 2; i++) {
66+
/* If the public key is NULL or invalid, xonly_pubkey_serialize will
67+
* call the illegal_callback and return 0. In that case we will
68+
* serialize the key as all zeros which is less than any valid public
69+
* key. This results in consistent comparisons even if NULL or invalid
70+
* pubkeys are involved and prevents edge cases such as sorting
71+
* algorithms that use this function and do not terminate as a
72+
* result. */
73+
if (!secp256k1_xonly_pubkey_serialize(ctx, out[i], pk[i])) {
74+
/* Note that xonly_pubkey_serialize should already set the output to
75+
* zero in that case, but it's not guaranteed by the API, we can't
76+
* test it and writing a VERIFY_CHECK is more complex than
77+
* explicitly memsetting (again). */
78+
memset(out[i], 0, sizeof(out[i]));
79+
}
80+
}
81+
return secp256k1_memcmp_var(out[0], out[1], sizeof(out[1]));
82+
}
83+
5884
/** Keeps a group element as is if it has an even Y and otherwise negates it.
5985
* y_parity is set to 0 in the former case and to 1 in the latter case.
6086
* Requires that the coordinates of r are normalized. */

src/modules/extrakeys/tests_impl.h

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,43 @@ void test_xonly_pubkey(void) {
137137
secp256k1_context_destroy(verify);
138138
}
139139

140+
void test_xonly_pubkey_comparison(void) {
141+
unsigned char pk1_ser[32] = {
142+
0x58, 0x84, 0xb3, 0xa2, 0x4b, 0x97, 0x37, 0x88, 0x92, 0x38, 0xa6, 0x26, 0x62, 0x52, 0x35, 0x11,
143+
0xd0, 0x9a, 0xa1, 0x1b, 0x80, 0x0b, 0x5e, 0x93, 0x80, 0x26, 0x11, 0xef, 0x67, 0x4b, 0xd9, 0x23
144+
};
145+
const unsigned char pk2_ser[32] = {
146+
0xde, 0x36, 0x0e, 0x87, 0x59, 0x8f, 0x3c, 0x01, 0x36, 0x2a, 0x2a, 0xb8, 0xc6, 0xf4, 0x5e, 0x4d,
147+
0xb2, 0xc2, 0xd5, 0x03, 0xa7, 0xf9, 0xf1, 0x4f, 0xa8, 0xfa, 0x95, 0xa8, 0xe9, 0x69, 0x76, 0x1c
148+
};
149+
secp256k1_xonly_pubkey pk1;
150+
secp256k1_xonly_pubkey pk2;
151+
int ecount = 0;
152+
secp256k1_context *none = api_test_context(SECP256K1_CONTEXT_NONE, &ecount);
153+
154+
CHECK(secp256k1_xonly_pubkey_parse(none, &pk1, pk1_ser) == 1);
155+
CHECK(secp256k1_xonly_pubkey_parse(none, &pk2, pk2_ser) == 1);
156+
157+
CHECK(secp256k1_xonly_pubkey_cmp(none, NULL, &pk2) < 0);
158+
CHECK(ecount == 1);
159+
CHECK(secp256k1_xonly_pubkey_cmp(none, &pk1, NULL) > 0);
160+
CHECK(ecount == 2);
161+
CHECK(secp256k1_xonly_pubkey_cmp(none, &pk1, &pk2) < 0);
162+
CHECK(secp256k1_xonly_pubkey_cmp(none, &pk2, &pk1) > 0);
163+
CHECK(secp256k1_xonly_pubkey_cmp(none, &pk1, &pk1) == 0);
164+
CHECK(secp256k1_xonly_pubkey_cmp(none, &pk2, &pk2) == 0);
165+
CHECK(ecount == 2);
166+
memset(&pk1, 0, sizeof(pk1)); /* illegal pubkey */
167+
CHECK(secp256k1_xonly_pubkey_cmp(none, &pk1, &pk2) < 0);
168+
CHECK(ecount == 3);
169+
CHECK(secp256k1_xonly_pubkey_cmp(none, &pk1, &pk1) == 0);
170+
CHECK(ecount == 5);
171+
CHECK(secp256k1_xonly_pubkey_cmp(none, &pk2, &pk1) > 0);
172+
CHECK(ecount == 6);
173+
174+
secp256k1_context_destroy(none);
175+
}
176+
140177
void test_xonly_pubkey_tweak(void) {
141178
unsigned char zeros64[64] = { 0 };
142179
unsigned char overflows[32];
@@ -540,6 +577,7 @@ void run_extrakeys_tests(void) {
540577
test_xonly_pubkey_tweak();
541578
test_xonly_pubkey_tweak_check();
542579
test_xonly_pubkey_tweak_recursive();
580+
test_xonly_pubkey_comparison();
543581

544582
/* keypair tests */
545583
test_keypair();

src/secp256k1.c

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,32 @@ int secp256k1_ec_pubkey_serialize(const secp256k1_context* ctx, unsigned char *o
322322
return ret;
323323
}
324324

325+
int secp256k1_ec_pubkey_cmp(const secp256k1_context* ctx, const secp256k1_pubkey* pubkey0, const secp256k1_pubkey* pubkey1) {
326+
unsigned char out[2][33];
327+
const secp256k1_pubkey* pk[2];
328+
int i;
329+
330+
VERIFY_CHECK(ctx != NULL);
331+
pk[0] = pubkey0; pk[1] = pubkey1;
332+
for (i = 0; i < 2; i++) {
333+
size_t out_size = sizeof(out[i]);
334+
/* If the public key is NULL or invalid, ec_pubkey_serialize will call
335+
* the illegal_callback and return 0. In that case we will serialize the
336+
* key as all zeros which is less than any valid public key. This
337+
* results in consistent comparisons even if NULL or invalid pubkeys are
338+
* involved and prevents edge cases such as sorting algorithms that use
339+
* this function and do not terminate as a result. */
340+
if (!secp256k1_ec_pubkey_serialize(ctx, out[i], &out_size, pk[i], SECP256K1_EC_COMPRESSED)) {
341+
/* Note that ec_pubkey_serialize should already set the output to
342+
* zero in that case, but it's not guaranteed by the API, we can't
343+
* test it and writing a VERIFY_CHECK is more complex than
344+
* explicitly memsetting (again). */
345+
memset(out[i], 0, sizeof(out[i]));
346+
}
347+
}
348+
return secp256k1_memcmp_var(out[0], out[1], sizeof(out[0]));
349+
}
350+
325351
static void secp256k1_ecdsa_signature_load(const secp256k1_context* ctx, secp256k1_scalar* r, secp256k1_scalar* s, const secp256k1_ecdsa_signature* sig) {
326352
(void)ctx;
327353
if (sizeof(secp256k1_scalar) == 32) {

src/tests.c

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5448,6 +5448,55 @@ void test_random_pubkeys(void) {
54485448
}
54495449
}
54505450

5451+
void run_pubkey_comparison(void) {
5452+
unsigned char pk1_ser[33] = {
5453+
0x02,
5454+
0x58, 0x84, 0xb3, 0xa2, 0x4b, 0x97, 0x37, 0x88, 0x92, 0x38, 0xa6, 0x26, 0x62, 0x52, 0x35, 0x11,
5455+
0xd0, 0x9a, 0xa1, 0x1b, 0x80, 0x0b, 0x5e, 0x93, 0x80, 0x26, 0x11, 0xef, 0x67, 0x4b, 0xd9, 0x23
5456+
};
5457+
const unsigned char pk2_ser[33] = {
5458+
0x02,
5459+
0xde, 0x36, 0x0e, 0x87, 0x59, 0x8f, 0x3c, 0x01, 0x36, 0x2a, 0x2a, 0xb8, 0xc6, 0xf4, 0x5e, 0x4d,
5460+
0xb2, 0xc2, 0xd5, 0x03, 0xa7, 0xf9, 0xf1, 0x4f, 0xa8, 0xfa, 0x95, 0xa8, 0xe9, 0x69, 0x76, 0x1c
5461+
};
5462+
secp256k1_pubkey pk1;
5463+
secp256k1_pubkey pk2;
5464+
int32_t ecount = 0;
5465+
5466+
CHECK(secp256k1_ec_pubkey_parse(ctx, &pk1, pk1_ser, sizeof(pk1_ser)) == 1);
5467+
CHECK(secp256k1_ec_pubkey_parse(ctx, &pk2, pk2_ser, sizeof(pk2_ser)) == 1);
5468+
5469+
secp256k1_context_set_illegal_callback(ctx, counting_illegal_callback_fn, &ecount);
5470+
CHECK(secp256k1_ec_pubkey_cmp(ctx, NULL, &pk2) < 0);
5471+
CHECK(ecount == 1);
5472+
CHECK(secp256k1_ec_pubkey_cmp(ctx, &pk1, NULL) > 0);
5473+
CHECK(ecount == 2);
5474+
CHECK(secp256k1_ec_pubkey_cmp(ctx, &pk1, &pk2) < 0);
5475+
CHECK(secp256k1_ec_pubkey_cmp(ctx, &pk2, &pk1) > 0);
5476+
CHECK(secp256k1_ec_pubkey_cmp(ctx, &pk1, &pk1) == 0);
5477+
CHECK(secp256k1_ec_pubkey_cmp(ctx, &pk2, &pk2) == 0);
5478+
CHECK(ecount == 2);
5479+
{
5480+
secp256k1_pubkey pk_tmp;
5481+
memset(&pk_tmp, 0, sizeof(pk_tmp)); /* illegal pubkey */
5482+
CHECK(secp256k1_ec_pubkey_cmp(ctx, &pk_tmp, &pk2) < 0);
5483+
CHECK(ecount == 3);
5484+
CHECK(secp256k1_ec_pubkey_cmp(ctx, &pk_tmp, &pk_tmp) == 0);
5485+
CHECK(ecount == 5);
5486+
CHECK(secp256k1_ec_pubkey_cmp(ctx, &pk2, &pk_tmp) > 0);
5487+
CHECK(ecount == 6);
5488+
}
5489+
5490+
secp256k1_context_set_illegal_callback(ctx, NULL, NULL);
5491+
5492+
/* Make pk2 the same as pk1 but with 3 rather than 2. Note that in
5493+
* an uncompressed encoding, these would have the opposite ordering */
5494+
pk1_ser[0] = 3;
5495+
CHECK(secp256k1_ec_pubkey_parse(ctx, &pk2, pk1_ser, sizeof(pk1_ser)) == 1);
5496+
CHECK(secp256k1_ec_pubkey_cmp(ctx, &pk1, &pk2) < 0);
5497+
CHECK(secp256k1_ec_pubkey_cmp(ctx, &pk2, &pk1) > 0);
5498+
}
5499+
54515500
void run_random_pubkeys(void) {
54525501
int i;
54535502
for (i = 0; i < 10*count; i++) {
@@ -6499,6 +6548,7 @@ int main(int argc, char **argv) {
64996548
#endif
65006549

65016550
/* ecdsa tests */
6551+
run_pubkey_comparison();
65026552
run_random_pubkeys();
65036553
run_ecdsa_der_parse();
65046554
run_ecdsa_sign_verify();

0 commit comments

Comments
 (0)