Skip to content

Commit 6eceec6

Browse files
committed
add secp256k1_xonly_pubkey_cmp method
1 parent 0d9561a commit 6eceec6

File tree

3 files changed

+82
-3
lines changed

3 files changed

+82
-3
lines changed

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();

0 commit comments

Comments
 (0)