Skip to content

Commit 0d9561a

Browse files
committed
add secp256k1_ec_pubkey_cmp method
1 parent cc2c09e commit 0d9561a

File tree

3 files changed

+94
-2
lines changed

3 files changed

+94
-2
lines changed

include/secp256k1.h

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

374+
/** Compare two public keys using lexicographic (of compressed serialization) order
375+
*
376+
* Returns: <0 if the first public key is less than the second
377+
* >0 if the first public key is greater than the second
378+
* 0 if the two public keys are equal
379+
* Args: ctx: a secp256k1 context object.
380+
* In: pubkey1: first public key to compare
381+
* pubkey2: second public key to compare
382+
*/
383+
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_pubkey_cmp(
384+
const secp256k1_context* ctx,
385+
const secp256k1_pubkey* pubkey1,
386+
const secp256k1_pubkey* pubkey2
387+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
388+
373389
/** Parse an ECDSA signature in compact (64 bytes) format.
374390
*
375391
* Returns: 1 when the signature could be parsed, 0 otherwise.

src/secp256k1.c

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

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

src/tests.c

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4809,6 +4809,55 @@ void test_random_pubkeys(void) {
48094809
}
48104810
}
48114811

4812+
void run_pubkey_comparison(void) {
4813+
unsigned char pk1_ser[33] = {
4814+
0x02,
4815+
0x58, 0x84, 0xb3, 0xa2, 0x4b, 0x97, 0x37, 0x88, 0x92, 0x38, 0xa6, 0x26, 0x62, 0x52, 0x35, 0x11,
4816+
0xd0, 0x9a, 0xa1, 0x1b, 0x80, 0x0b, 0x5e, 0x93, 0x80, 0x26, 0x11, 0xef, 0x67, 0x4b, 0xd9, 0x23
4817+
};
4818+
const unsigned char pk2_ser[33] = {
4819+
0x02,
4820+
0xde, 0x36, 0x0e, 0x87, 0x59, 0x8f, 0x3c, 0x01, 0x36, 0x2a, 0x2a, 0xb8, 0xc6, 0xf4, 0x5e, 0x4d,
4821+
0xb2, 0xc2, 0xd5, 0x03, 0xa7, 0xf9, 0xf1, 0x4f, 0xa8, 0xfa, 0x95, 0xa8, 0xe9, 0x69, 0x76, 0x1c
4822+
};
4823+
secp256k1_pubkey pk1;
4824+
secp256k1_pubkey pk2;
4825+
int32_t ecount = 0;
4826+
4827+
CHECK(secp256k1_ec_pubkey_parse(ctx, &pk1, pk1_ser, sizeof(pk1_ser)) == 1);
4828+
CHECK(secp256k1_ec_pubkey_parse(ctx, &pk2, pk2_ser, sizeof(pk2_ser)) == 1);
4829+
4830+
secp256k1_context_set_illegal_callback(ctx, counting_illegal_callback_fn, &ecount);
4831+
CHECK(secp256k1_ec_pubkey_cmp(ctx, NULL, &pk2) < 0);
4832+
CHECK(ecount == 1);
4833+
CHECK(secp256k1_ec_pubkey_cmp(ctx, &pk1, NULL) > 0);
4834+
CHECK(ecount == 2);
4835+
CHECK(secp256k1_ec_pubkey_cmp(ctx, &pk1, &pk2) < 0);
4836+
CHECK(secp256k1_ec_pubkey_cmp(ctx, &pk2, &pk1) > 0);
4837+
CHECK(secp256k1_ec_pubkey_cmp(ctx, &pk1, &pk1) == 0);
4838+
CHECK(secp256k1_ec_pubkey_cmp(ctx, &pk2, &pk2) == 0);
4839+
CHECK(ecount == 2);
4840+
{
4841+
secp256k1_pubkey pk_tmp;
4842+
memset(&pk_tmp, 0, sizeof(pk_tmp)); /* illegal pubkey */
4843+
CHECK(secp256k1_ec_pubkey_cmp(ctx, &pk_tmp, &pk2) < 0);
4844+
CHECK(ecount == 3);
4845+
CHECK(secp256k1_ec_pubkey_cmp(ctx, &pk_tmp, &pk_tmp) == 0);
4846+
CHECK(ecount == 5);
4847+
CHECK(secp256k1_ec_pubkey_cmp(ctx, &pk2, &pk_tmp) > 0);
4848+
CHECK(ecount == 6);
4849+
}
4850+
4851+
secp256k1_context_set_illegal_callback(ctx, NULL, NULL);
4852+
4853+
/* Make pk2 the same as pk1 but with 3 rather than 2. Note that in
4854+
* an uncompressed encoding, these would have the opposite ordering */
4855+
pk1_ser[0] = 3;
4856+
CHECK(secp256k1_ec_pubkey_parse(ctx, &pk2, pk1_ser, sizeof(pk1_ser)) == 1);
4857+
CHECK(secp256k1_ec_pubkey_cmp(ctx, &pk1, &pk2) < 0);
4858+
CHECK(secp256k1_ec_pubkey_cmp(ctx, &pk2, &pk1) > 0);
4859+
}
4860+
48124861
void run_random_pubkeys(void) {
48134862
int i;
48144863
for (i = 0; i < 10*count; i++) {
@@ -5860,6 +5909,7 @@ int main(int argc, char **argv) {
58605909
#endif
58615910

58625911
/* ecdsa tests */
5912+
run_pubkey_comparison();
58635913
run_random_pubkeys();
58645914
run_ecdsa_der_parse();
58655915
run_ecdsa_sign_verify();

0 commit comments

Comments
 (0)