Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit cc7d18a

Browse files
committedApr 16, 2024··
extrakeys: add secp256k1_pubkey_sort
1 parent d831168 commit cc7d18a

File tree

7 files changed

+388
-1
lines changed

7 files changed

+388
-1
lines changed
 

‎Makefile.am

+2
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ noinst_HEADERS += src/field.h
6464
noinst_HEADERS += src/field_impl.h
6565
noinst_HEADERS += src/bench.h
6666
noinst_HEADERS += src/wycheproof/ecdsa_secp256k1_sha256_bitcoin_test.h
67+
noinst_HEADERS += src/hsort.h
68+
noinst_HEADERS += src/hsort_impl.h
6769
noinst_HEADERS += contrib/lax_der_parsing.h
6870
noinst_HEADERS += contrib/lax_der_parsing.c
6971
noinst_HEADERS += contrib/lax_der_privatekey_parsing.h

‎include/secp256k1.h

+14
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,20 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_pubkey_cmp(
474474
const secp256k1_pubkey *pubkey2
475475
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
476476

477+
/** Sort public keys keys using lexicographic (of compressed serialization) order
478+
*
479+
* Returns: 0 if the arguments are invalid. 1 otherwise.
480+
*
481+
* Args: ctx: pointer to a context object
482+
* In: pubkeys: array of pointers to pubkeys to sort
483+
* n_pubkeys: number of elements in the pubkeys array
484+
*/
485+
SECP256K1_API int secp256k1_ec_pubkey_sort(
486+
const secp256k1_context *ctx,
487+
const secp256k1_pubkey **pubkeys,
488+
size_t n_pubkeys
489+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2);
490+
477491
/** Parse an ECDSA signature in compact (64 bytes) format.
478492
*
479493
* Returns: 1 when the signature could be parsed, 0 otherwise.

‎src/hsort.h

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/***********************************************************************
2+
* Copyright (c) 2021 Russell O'Connor, Jonas Nick *
3+
* Distributed under the MIT software license, see the accompanying *
4+
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
5+
***********************************************************************/
6+
7+
#ifndef SECP256K1_HSORT_H
8+
#define SECP256K1_HSORT_H
9+
10+
#include <stddef.h>
11+
#include <string.h>
12+
13+
/* In-place, iterative heapsort with an interface matching glibc's qsort_r. This
14+
* is preferred over standard library implementations because they generally
15+
* make no guarantee about being fast for malicious inputs.
16+
*
17+
* See the qsort_r manpage for a description of the interface.
18+
*/
19+
static void secp256k1_hsort(void *ptr, size_t count, size_t size,
20+
int (*cmp)(const void *, const void *, void *),
21+
void *cmp_data);
22+
#endif

‎src/hsort_impl.h

+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/***********************************************************************
2+
* Copyright (c) 2021 Russell O'Connor, Jonas Nick *
3+
* Distributed under the MIT software license, see the accompanying *
4+
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
5+
***********************************************************************/
6+
7+
#ifndef SECP256K1_HSORT_IMPL_H
8+
#define SECP256K1_HSORT_IMPL_H
9+
10+
#include "hsort.h"
11+
12+
/* An array is a heap when, for all non-zero indexes i, the element at index i
13+
* compares as less than or equal to the element at index parent(i) = (i-1)/2.
14+
*/
15+
16+
static SECP256K1_INLINE size_t child1(size_t i) {
17+
VERIFY_CHECK(i <= (SIZE_MAX - 1)/2);
18+
return 2*i + 1;
19+
}
20+
21+
static SECP256K1_INLINE size_t child2(size_t i) {
22+
VERIFY_CHECK(i <= SIZE_MAX/2 - 1);
23+
return child1(i)+1;
24+
}
25+
26+
static SECP256K1_INLINE void heap_swap64(unsigned char *a, size_t i, size_t j, size_t stride) {
27+
unsigned char tmp[64];
28+
VERIFY_CHECK(stride <= 64);
29+
memcpy(tmp, a + i*stride, stride);
30+
memmove(a + i*stride, a + j*stride, stride);
31+
memcpy(a + j*stride, tmp, stride);
32+
}
33+
34+
static SECP256K1_INLINE void heap_swap(unsigned char *a, size_t i, size_t j, size_t stride) {
35+
while (64 < stride) {
36+
heap_swap64(a + (stride - 64), i, j, 64);
37+
stride -= 64;
38+
}
39+
heap_swap64(a, i, j, stride);
40+
}
41+
42+
static SECP256K1_INLINE void heap_down(unsigned char *a, size_t i, size_t heap_size, size_t stride,
43+
int (*cmp)(const void *, const void *, void *), void *cmp_data) {
44+
while (i < heap_size/2) {
45+
VERIFY_CHECK(i <= SIZE_MAX/2 - 1);
46+
/* Proof:
47+
* i < heap_size/2
48+
* i + 1 <= heap_size/2
49+
* 2*i + 2 <= heap_size <= SIZE_MAX
50+
* 2*i <= SIZE_MAX - 2
51+
*/
52+
53+
VERIFY_CHECK(child1(i) < heap_size);
54+
/* Proof:
55+
* i < heap_size/2
56+
* i + 1 <= heap_size/2
57+
* 2*i + 2 <= heap_size
58+
* 2*i + 1 < heap_size
59+
* child1(i) < heap_size
60+
*/
61+
62+
/* Let [x] be notation for the contents at a[x*stride].
63+
*
64+
* If [child1(i)] > [i] and [child2(i)] > [i],
65+
* swap [i] with the larger child to ensure the new parent is larger
66+
* than both children. When [child1(i)] == [child2(i)], swap [i] with
67+
* [child2(i)].
68+
* Else if [child1(i)] > [i], swap [i] with [child1(i)].
69+
* Else if [child2(i)] > [i], swap [i] with [child2(i)].
70+
*/
71+
if (child2(i) < heap_size
72+
&& 0 <= cmp(a + child2(i)*stride, a + child1(i)*stride, cmp_data)) {
73+
if (0 < cmp(a + child2(i)*stride, a + i*stride, cmp_data)) {
74+
heap_swap(a, i, child2(i), stride);
75+
i = child2(i);
76+
} else {
77+
/* At this point we have [child2(i)] >= [child1(i)] and we have
78+
* [child2(i)] <= [i], and thus [child1(i)] <= [i] which means
79+
* that the next comparison can be skipped. */
80+
return;
81+
}
82+
} else if (0 < cmp(a + child1(i)*stride, a + i*stride, cmp_data)) {
83+
heap_swap(a, i, child1(i), stride);
84+
i = child1(i);
85+
} else {
86+
return;
87+
}
88+
}
89+
/* heap_size/2 <= i
90+
* heap_size/2 < i + 1
91+
* heap_size < 2*i + 2
92+
* heap_size <= 2*i + 1
93+
* heap_size <= child1(i)
94+
* Thus child1(i) and child2(i) are now out of bounds and we are at a leaf.
95+
*/
96+
}
97+
98+
/* In-place heap sort. */
99+
static void secp256k1_hsort(void *ptr, size_t count, size_t size,
100+
int (*cmp)(const void *, const void *, void *),
101+
void *cmp_data ) {
102+
size_t i;
103+
104+
for(i = count/2; 0 < i; --i) {
105+
heap_down(ptr, i-1, count, size, cmp, cmp_data);
106+
}
107+
for(i = count; 1 < i; --i) {
108+
/* Extract the largest value from the heap */
109+
heap_swap(ptr, 0, i-1, size);
110+
111+
/* Repair the heap condition */
112+
heap_down(ptr, 0, i-1, size, cmp, cmp_data);
113+
}
114+
}
115+
116+
#endif
+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
include_HEADERS += include/secp256k1_extrakeys.h
22
noinst_HEADERS += src/modules/extrakeys/tests_impl.h
33
noinst_HEADERS += src/modules/extrakeys/tests_exhaustive_impl.h
4-
noinst_HEADERS += src/modules/extrakeys/main_impl.h
4+
noinst_HEADERS += src/modules/extrakeys/main_impl.h

‎src/secp256k1.c

+35
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include "int128_impl.h"
3737
#include "scratch_impl.h"
3838
#include "selftest.h"
39+
#include "hsort_impl.h"
3940

4041
#ifdef SECP256K1_NO_BUILD
4142
# error "secp256k1.h processed without SECP256K1_BUILD defined while building secp256k1.c"
@@ -325,6 +326,40 @@ int secp256k1_ec_pubkey_cmp(const secp256k1_context* ctx, const secp256k1_pubkey
325326
return secp256k1_memcmp_var(out[0], out[1], sizeof(out[0]));
326327
}
327328

329+
/* This struct wraps a const context pointer to satisfy the secp256k1_hsort api
330+
* which expects a non-const cmp_data pointer. */
331+
typedef struct {
332+
const secp256k1_context *ctx;
333+
} secp256k1_ec_pubkey_sort_cmp_data;
334+
335+
static int secp256k1_ec_pubkey_sort_cmp(const void* pk1, const void* pk2, void *cmp_data) {
336+
return secp256k1_ec_pubkey_cmp(((secp256k1_ec_pubkey_sort_cmp_data*)cmp_data)->ctx,
337+
*(secp256k1_pubkey **)pk1,
338+
*(secp256k1_pubkey **)pk2);
339+
}
340+
341+
int secp256k1_ec_pubkey_sort(const secp256k1_context* ctx, const secp256k1_pubkey **pubkeys, size_t n_pubkeys) {
342+
secp256k1_ec_pubkey_sort_cmp_data cmp_data;
343+
VERIFY_CHECK(ctx != NULL);
344+
ARG_CHECK(pubkeys != NULL);
345+
346+
cmp_data.ctx = ctx;
347+
348+
/* Suppress wrong warning (fixed in MSVC 19.33) */
349+
#if defined(_MSC_VER) && (_MSC_VER < 1933)
350+
#pragma warning(push)
351+
#pragma warning(disable: 4090)
352+
#endif
353+
354+
secp256k1_hsort(pubkeys, n_pubkeys, sizeof(*pubkeys), secp256k1_ec_pubkey_sort_cmp, &cmp_data);
355+
356+
#if defined(_MSC_VER) && (_MSC_VER < 1933)
357+
#pragma warning(pop)
358+
#endif
359+
360+
return 1;
361+
}
362+
328363
static void secp256k1_ecdsa_signature_load(const secp256k1_context* ctx, secp256k1_scalar* r, secp256k1_scalar* s, const secp256k1_ecdsa_signature* sig) {
329364
(void)ctx;
330365
if (sizeof(secp256k1_scalar) == 32) {

‎src/tests.c

+198
Original file line numberDiff line numberDiff line change
@@ -6607,6 +6607,203 @@ static void run_pubkey_comparison(void) {
66076607
CHECK(secp256k1_ec_pubkey_cmp(CTX, &pk2, &pk1) > 0);
66086608
}
66096609

6610+
6611+
static void test_hsort_is_sorted(int *ints, size_t n) {
6612+
size_t i;
6613+
for (i = 1; i < n; i++) {
6614+
CHECK(ints[i-1] <= ints[i]);
6615+
}
6616+
}
6617+
6618+
static int test_hsort_cmp(const void *i1, const void *i2, void *counter) {
6619+
*(size_t*)counter += 1;
6620+
return *(int*)i1 - *(int*)i2;
6621+
}
6622+
6623+
#define NUM 64
6624+
static void test_hsort(void) {
6625+
int ints[NUM] = { 0 };
6626+
size_t counter = 0;
6627+
int i, j;
6628+
6629+
secp256k1_hsort(ints, 0, sizeof(ints[0]), test_hsort_cmp, &counter);
6630+
CHECK(counter == 0);
6631+
secp256k1_hsort(ints, 1, sizeof(ints[0]), test_hsort_cmp, &counter);
6632+
CHECK(counter == 0);
6633+
secp256k1_hsort(ints, NUM, sizeof(ints[0]), test_hsort_cmp, &counter);
6634+
CHECK(counter > 0);
6635+
test_hsort_is_sorted(ints, NUM);
6636+
6637+
/* Test hsort with length n array and random elements in
6638+
* [-interval/2, interval/2] */
6639+
for (i = 0; i < COUNT; i++) {
6640+
int n = secp256k1_testrand_int(NUM);
6641+
int interval = secp256k1_testrand_int(63) + 1;
6642+
for (j = 0; j < n; j++) {
6643+
ints[j] = secp256k1_testrand_int(interval) - interval/2;
6644+
}
6645+
secp256k1_hsort(ints, n, sizeof(ints[0]), test_hsort_cmp, &counter);
6646+
test_hsort_is_sorted(ints, n);
6647+
}
6648+
}
6649+
#undef NUM
6650+
6651+
static void test_sort_helper(secp256k1_pubkey *pk, size_t *pk_order, size_t n_pk) {
6652+
size_t i;
6653+
const secp256k1_pubkey *pk_test[5];
6654+
6655+
for (i = 0; i < n_pk; i++) {
6656+
pk_test[i] = &pk[pk_order[i]];
6657+
}
6658+
secp256k1_ec_pubkey_sort(CTX, pk_test, n_pk);
6659+
for (i = 0; i < n_pk; i++) {
6660+
CHECK(secp256k1_memcmp_var(pk_test[i], &pk[i], sizeof(*pk_test[i])) == 0);
6661+
}
6662+
}
6663+
6664+
static void permute(size_t *arr, size_t n) {
6665+
size_t i;
6666+
for (i = n - 1; i >= 1; i--) {
6667+
size_t tmp, j;
6668+
j = secp256k1_testrand_int(i + 1);
6669+
tmp = arr[i];
6670+
arr[i] = arr[j];
6671+
arr[j] = tmp;
6672+
}
6673+
}
6674+
6675+
static void rand_pk(secp256k1_pubkey *pk) {
6676+
unsigned char seckey[32];
6677+
secp256k1_keypair keypair;
6678+
secp256k1_testrand256(seckey);
6679+
CHECK(secp256k1_keypair_create(CTX, &keypair, seckey) == 1);
6680+
CHECK(secp256k1_keypair_pub(CTX, pk, &keypair) == 1);
6681+
}
6682+
6683+
static void test_sort_api(void) {
6684+
secp256k1_pubkey pks[2];
6685+
const secp256k1_pubkey *pks_ptr[2];
6686+
6687+
pks_ptr[0] = &pks[0];
6688+
pks_ptr[1] = &pks[1];
6689+
6690+
rand_pk(&pks[0]);
6691+
rand_pk(&pks[1]);
6692+
6693+
CHECK(secp256k1_ec_pubkey_sort(CTX, pks_ptr, 2) == 1);
6694+
CHECK_ILLEGAL(CTX, secp256k1_ec_pubkey_sort(CTX, NULL, 2));
6695+
CHECK(secp256k1_ec_pubkey_sort(CTX, pks_ptr, 0) == 1);
6696+
/* Test illegal public keys */
6697+
memset(&pks[0], 0, sizeof(pks[0]));
6698+
CHECK_ILLEGAL_VOID(CTX, CHECK(secp256k1_ec_pubkey_sort(CTX, pks_ptr, 2) == 1));
6699+
memset(&pks[1], 0, sizeof(pks[1]));
6700+
{
6701+
int32_t ecount = 0;
6702+
secp256k1_context_set_illegal_callback(CTX, counting_callback_fn, &ecount);
6703+
CHECK(secp256k1_ec_pubkey_sort(CTX, pks_ptr, 2) == 1);
6704+
CHECK(ecount == 2);
6705+
secp256k1_context_set_illegal_callback(CTX, NULL, NULL);
6706+
}
6707+
}
6708+
6709+
static void test_sort(void) {
6710+
secp256k1_pubkey pk[5];
6711+
unsigned char pk_ser[5][33] = {
6712+
{ 0x02, 0x08 },
6713+
{ 0x02, 0x0b },
6714+
{ 0x02, 0x0c },
6715+
{ 0x03, 0x05 },
6716+
{ 0x03, 0x0a },
6717+
};
6718+
int i;
6719+
size_t pk_order[5] = { 0, 1, 2, 3, 4 };
6720+
6721+
for (i = 0; i < 5; i++) {
6722+
CHECK(secp256k1_ec_pubkey_parse(CTX, &pk[i], pk_ser[i], sizeof(pk_ser[i])));
6723+
}
6724+
6725+
permute(pk_order, 1);
6726+
test_sort_helper(pk, pk_order, 1);
6727+
permute(pk_order, 2);
6728+
test_sort_helper(pk, pk_order, 2);
6729+
permute(pk_order, 3);
6730+
test_sort_helper(pk, pk_order, 3);
6731+
for (i = 0; i < COUNT; i++) {
6732+
permute(pk_order, 4);
6733+
test_sort_helper(pk, pk_order, 4);
6734+
}
6735+
for (i = 0; i < COUNT; i++) {
6736+
permute(pk_order, 5);
6737+
test_sort_helper(pk, pk_order, 5);
6738+
}
6739+
/* Check that sorting also works for random pubkeys */
6740+
for (i = 0; i < COUNT; i++) {
6741+
int j;
6742+
const secp256k1_pubkey *pk_ptr[5];
6743+
for (j = 0; j < 5; j++) {
6744+
rand_pk(&pk[j]);
6745+
pk_ptr[j] = &pk[j];
6746+
}
6747+
secp256k1_ec_pubkey_sort(CTX, pk_ptr, 5);
6748+
for (j = 1; j < 5; j++) {
6749+
CHECK(secp256k1_ec_pubkey_sort_cmp(&pk_ptr[j - 1], &pk_ptr[j], CTX) <= 0);
6750+
}
6751+
}
6752+
}
6753+
6754+
/* Test vectors from BIP-MuSig2 */
6755+
static void test_sort_vectors(void) {
6756+
enum { N_PUBKEYS = 6 };
6757+
unsigned char pk_ser[N_PUBKEYS][33] = {
6758+
{ 0x02, 0xDD, 0x30, 0x8A, 0xFE, 0xC5, 0x77, 0x7E, 0x13, 0x12, 0x1F,
6759+
0xA7, 0x2B, 0x9C, 0xC1, 0xB7, 0xCC, 0x01, 0x39, 0x71, 0x53, 0x09,
6760+
0xB0, 0x86, 0xC9, 0x60, 0xE1, 0x8F, 0xD9, 0x69, 0x77, 0x4E, 0xB8 },
6761+
{ 0x02, 0xF9, 0x30, 0x8A, 0x01, 0x92, 0x58, 0xC3, 0x10, 0x49, 0x34,
6762+
0x4F, 0x85, 0xF8, 0x9D, 0x52, 0x29, 0xB5, 0x31, 0xC8, 0x45, 0x83,
6763+
0x6F, 0x99, 0xB0, 0x86, 0x01, 0xF1, 0x13, 0xBC, 0xE0, 0x36, 0xF9 },
6764+
{ 0x03, 0xDF, 0xF1, 0xD7, 0x7F, 0x2A, 0x67, 0x1C, 0x5F, 0x36, 0x18,
6765+
0x37, 0x26, 0xDB, 0x23, 0x41, 0xBE, 0x58, 0xFE, 0xAE, 0x1D, 0xA2,
6766+
0xDE, 0xCE, 0xD8, 0x43, 0x24, 0x0F, 0x7B, 0x50, 0x2B, 0xA6, 0x59 },
6767+
{ 0x02, 0x35, 0x90, 0xA9, 0x4E, 0x76, 0x8F, 0x8E, 0x18, 0x15, 0xC2,
6768+
0xF2, 0x4B, 0x4D, 0x80, 0xA8, 0xE3, 0x14, 0x93, 0x16, 0xC3, 0x51,
6769+
0x8C, 0xE7, 0xB7, 0xAD, 0x33, 0x83, 0x68, 0xD0, 0x38, 0xCA, 0x66 },
6770+
{ 0x02, 0xDD, 0x30, 0x8A, 0xFE, 0xC5, 0x77, 0x7E, 0x13, 0x12, 0x1F,
6771+
0xA7, 0x2B, 0x9C, 0xC1, 0xB7, 0xCC, 0x01, 0x39, 0x71, 0x53, 0x09,
6772+
0xB0, 0x86, 0xC9, 0x60, 0xE1, 0x8F, 0xD9, 0x69, 0x77, 0x4E, 0xFF },
6773+
{ 0x02, 0xDD, 0x30, 0x8A, 0xFE, 0xC5, 0x77, 0x7E, 0x13, 0x12, 0x1F,
6774+
0xA7, 0x2B, 0x9C, 0xC1, 0xB7, 0xCC, 0x01, 0x39, 0x71, 0x53, 0x09,
6775+
0xB0, 0x86, 0xC9, 0x60, 0xE1, 0x8F, 0xD9, 0x69, 0x77, 0x4E, 0xB8 }
6776+
};
6777+
secp256k1_pubkey pubkeys[N_PUBKEYS];
6778+
secp256k1_pubkey *sorted[N_PUBKEYS];
6779+
const secp256k1_pubkey *pks_ptr[N_PUBKEYS];
6780+
int i;
6781+
6782+
sorted[0] = &pubkeys[3];
6783+
sorted[1] = &pubkeys[0];
6784+
sorted[2] = &pubkeys[0];
6785+
sorted[3] = &pubkeys[4];
6786+
sorted[4] = &pubkeys[1];
6787+
sorted[5] = &pubkeys[2];
6788+
6789+
for (i = 0; i < N_PUBKEYS; i++) {
6790+
CHECK(secp256k1_ec_pubkey_parse(CTX, &pubkeys[i], pk_ser[i], sizeof(pk_ser[i])));
6791+
pks_ptr[i] = &pubkeys[i];
6792+
}
6793+
CHECK(secp256k1_ec_pubkey_sort(CTX, pks_ptr, N_PUBKEYS) == 1);
6794+
for (i = 0; i < N_PUBKEYS; i++) {
6795+
CHECK(secp256k1_memcmp_var(pks_ptr[i], sorted[i], sizeof(secp256k1_pubkey)) == 0);
6796+
}
6797+
}
6798+
6799+
static void run_pubkey_sort(void) {
6800+
test_hsort();
6801+
test_sort_api();
6802+
test_sort();
6803+
test_sort_vectors();
6804+
}
6805+
6806+
66106807
static void run_random_pubkeys(void) {
66116808
int i;
66126809
for (i = 0; i < 10*COUNT; i++) {
@@ -7622,6 +7819,7 @@ int main(int argc, char **argv) {
76227819
/* ecdsa tests */
76237820
run_ec_illegal_argument_tests();
76247821
run_pubkey_comparison();
7822+
run_pubkey_sort();
76257823
run_random_pubkeys();
76267824
run_ecdsa_der_parse();
76277825
run_ecdsa_sign_verify();

0 commit comments

Comments
 (0)
Please sign in to comment.