Skip to content

Commit 4cd2ee4

Browse files
committed
extrakeys: Add xonly_pubkey with serialize, parse and from_pubkey
1 parent 47e6618 commit 4cd2ee4

File tree

3 files changed

+274
-1
lines changed

3 files changed

+274
-1
lines changed

include/secp256k1_extrakeys.h

+69
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,75 @@
77
extern "C" {
88
#endif
99

10+
/** Opaque data structure that holds a parsed and valid "x-only" public key.
11+
* An x-only pubkey encodes a point whose Y coordinate is even. It is
12+
* serialized using only its X coordinate (32 bytes). See BIP-340 for more
13+
* information about x-only pubkeys.
14+
*
15+
* The exact representation of data inside is implementation defined and not
16+
* guaranteed to be portable between different platforms or versions. It is
17+
* 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.
21+
*/
22+
typedef struct {
23+
unsigned char data[64];
24+
} secp256k1_xonly_pubkey;
25+
26+
/** Parse a 32-byte sequence into a xonly_pubkey object.
27+
*
28+
* Returns: 1 if the public key was fully valid.
29+
* 0 if the public key could not be parsed or is invalid.
30+
*
31+
* Args: ctx: a secp256k1 context object (cannot be NULL).
32+
* Out: pubkey: pointer to a pubkey object. If 1 is returned, it is set to a
33+
* parsed version of input. If not, it's set to an invalid value.
34+
* (cannot be NULL).
35+
* In: input32: pointer to a serialized xonly_pubkey (cannot be NULL)
36+
*/
37+
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_xonly_pubkey_parse(
38+
const secp256k1_context* ctx,
39+
secp256k1_xonly_pubkey* pubkey,
40+
const unsigned char *input32
41+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
42+
43+
/** Serialize an xonly_pubkey object into a 32-byte sequence.
44+
*
45+
* Returns: 1 always.
46+
*
47+
* Args: ctx: a secp256k1 context object (cannot be NULL).
48+
* Out: output32: a pointer to a 32-byte array to place the serialized key in
49+
* (cannot be NULL).
50+
* In: pubkey: a pointer to a secp256k1_xonly_pubkey containing an
51+
* initialized public key (cannot be NULL).
52+
*/
53+
SECP256K1_API int secp256k1_xonly_pubkey_serialize(
54+
const secp256k1_context* ctx,
55+
unsigned char *output32,
56+
const secp256k1_xonly_pubkey* pubkey
57+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
58+
59+
/** Converts a secp256k1_pubkey into a secp256k1_xonly_pubkey.
60+
*
61+
* Returns: 1 if the public key was successfully converted
62+
* 0 otherwise
63+
*
64+
* Args: ctx: pointer to a context object (cannot be NULL)
65+
* Out: xonly_pubkey: pointer to an x-only public key object for placing the
66+
* converted public key (cannot be NULL)
67+
* pk_parity: pointer to an integer that will be set to 1 if the point
68+
* encoded by xonly_pubkey is the negation of the pubkey and
69+
* set to 0 otherwise. (can be NULL)
70+
* In: pubkey: pointer to a public key that is converted (cannot be NULL)
71+
*/
72+
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_xonly_pubkey_from_pubkey(
73+
const secp256k1_context* ctx,
74+
secp256k1_xonly_pubkey *xonly_pubkey,
75+
int *pk_parity,
76+
const secp256k1_pubkey *pubkey
77+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4);
78+
1079
#ifdef __cplusplus
1180
}
1281
#endif

src/modules/extrakeys/main_impl.h

+75
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,79 @@
1010
#include "include/secp256k1.h"
1111
#include "include/secp256k1_extrakeys.h"
1212

13+
static SECP256K1_INLINE int secp256k1_xonly_pubkey_load(const secp256k1_context* ctx, secp256k1_ge *ge, const secp256k1_xonly_pubkey *pubkey) {
14+
return secp256k1_pubkey_load(ctx, ge, (const secp256k1_pubkey *) pubkey);
15+
}
16+
17+
static SECP256K1_INLINE void secp256k1_xonly_pubkey_save(secp256k1_xonly_pubkey *pubkey, secp256k1_ge *ge) {
18+
secp256k1_pubkey_save((secp256k1_pubkey *) pubkey, ge);
19+
}
20+
21+
int secp256k1_xonly_pubkey_parse(const secp256k1_context* ctx, secp256k1_xonly_pubkey *pubkey, const unsigned char *input32) {
22+
secp256k1_ge pk;
23+
secp256k1_fe x;
24+
25+
VERIFY_CHECK(ctx != NULL);
26+
ARG_CHECK(pubkey != NULL);
27+
memset(pubkey, 0, sizeof(*pubkey));
28+
ARG_CHECK(input32 != NULL);
29+
30+
if (!secp256k1_fe_set_b32(&x, input32)) {
31+
return 0;
32+
}
33+
if (!secp256k1_ge_set_xo_var(&pk, &x, 0)) {
34+
return 0;
35+
}
36+
secp256k1_xonly_pubkey_save(pubkey, &pk);
37+
return 1;
38+
}
39+
40+
int secp256k1_xonly_pubkey_serialize(const secp256k1_context* ctx, unsigned char *output32, const secp256k1_xonly_pubkey *pubkey) {
41+
secp256k1_ge pk;
42+
43+
VERIFY_CHECK(ctx != NULL);
44+
ARG_CHECK(output32 != NULL);
45+
memset(output32, 0, 32);
46+
ARG_CHECK(pubkey != NULL);
47+
48+
if (!secp256k1_xonly_pubkey_load(ctx, &pk, pubkey)) {
49+
return 0;
50+
}
51+
secp256k1_fe_get_b32(output32, &pk.x);
52+
return 1;
53+
}
54+
55+
/** Keeps a group element as is if it has an even Y and otherwise negates it.
56+
* y_parity is set to 0 in the former case and to 1 in the latter case.
57+
* Requires that the coordinates of r are normalized. */
58+
static int secp256k1_extrakeys_ge_even_y(secp256k1_ge *r) {
59+
int y_parity = 0;
60+
VERIFY_CHECK(!secp256k1_ge_is_infinity(r));
61+
62+
if (secp256k1_fe_is_odd(&r->y)) {
63+
secp256k1_fe_negate(&r->y, &r->y, 1);
64+
y_parity = 1;
65+
}
66+
return y_parity;
67+
}
68+
69+
int secp256k1_xonly_pubkey_from_pubkey(const secp256k1_context* ctx, secp256k1_xonly_pubkey *xonly_pubkey, int *pk_parity, const secp256k1_pubkey *pubkey) {
70+
secp256k1_ge pk;
71+
int tmp;
72+
73+
VERIFY_CHECK(ctx != NULL);
74+
ARG_CHECK(xonly_pubkey != NULL);
75+
ARG_CHECK(pubkey != NULL);
76+
77+
if (!secp256k1_pubkey_load(ctx, &pk, pubkey)) {
78+
return 0;
79+
}
80+
tmp = secp256k1_extrakeys_ge_even_y(&pk);
81+
if (pk_parity != NULL) {
82+
*pk_parity = tmp;
83+
}
84+
secp256k1_xonly_pubkey_save(xonly_pubkey, &pk);
85+
return 1;
86+
}
87+
1388
#endif

src/modules/extrakeys/tests_impl.h

+130-1
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,137 @@
99

1010
#include "secp256k1_extrakeys.h"
1111

12+
static secp256k1_context* api_test_context(int flags, int *ecount) {
13+
secp256k1_context *ctx0 = secp256k1_context_create(flags);
14+
secp256k1_context_set_error_callback(ctx0, counting_illegal_callback_fn, ecount);
15+
secp256k1_context_set_illegal_callback(ctx0, counting_illegal_callback_fn, ecount);
16+
return ctx0;
17+
}
18+
19+
void test_xonly_pubkey(void) {
20+
secp256k1_pubkey pk;
21+
secp256k1_xonly_pubkey xonly_pk, xonly_pk_tmp;
22+
secp256k1_ge pk1;
23+
secp256k1_ge pk2;
24+
secp256k1_fe y;
25+
unsigned char sk[32];
26+
unsigned char xy_sk[32];
27+
unsigned char buf32[32];
28+
unsigned char ones32[32];
29+
unsigned char zeros64[64] = { 0 };
30+
int pk_parity;
31+
int i;
32+
33+
int ecount;
34+
secp256k1_context *none = api_test_context(SECP256K1_CONTEXT_NONE, &ecount);
35+
secp256k1_context *sign = api_test_context(SECP256K1_CONTEXT_SIGN, &ecount);
36+
secp256k1_context *verify = api_test_context(SECP256K1_CONTEXT_VERIFY, &ecount);
37+
38+
secp256k1_rand256(sk);
39+
memset(ones32, 0xFF, 32);
40+
secp256k1_rand256(xy_sk);
41+
CHECK(secp256k1_ec_pubkey_create(sign, &pk, sk) == 1);
42+
CHECK(secp256k1_xonly_pubkey_from_pubkey(none, &xonly_pk, &pk_parity, &pk) == 1);
43+
44+
/* Test xonly_pubkey_from_pubkey */
45+
ecount = 0;
46+
CHECK(secp256k1_xonly_pubkey_from_pubkey(none, &xonly_pk, &pk_parity, &pk) == 1);
47+
CHECK(secp256k1_xonly_pubkey_from_pubkey(sign, &xonly_pk, &pk_parity, &pk) == 1);
48+
CHECK(secp256k1_xonly_pubkey_from_pubkey(verify, &xonly_pk, &pk_parity, &pk) == 1);
49+
CHECK(secp256k1_xonly_pubkey_from_pubkey(none, NULL, &pk_parity, &pk) == 0);
50+
CHECK(ecount == 1);
51+
CHECK(secp256k1_xonly_pubkey_from_pubkey(none, &xonly_pk, NULL, &pk) == 1);
52+
CHECK(secp256k1_xonly_pubkey_from_pubkey(none, &xonly_pk, &pk_parity, NULL) == 0);
53+
CHECK(ecount == 2);
54+
memset(&pk, 0, sizeof(pk));
55+
CHECK(secp256k1_xonly_pubkey_from_pubkey(none, &xonly_pk, &pk_parity, &pk) == 0);
56+
CHECK(ecount == 3);
57+
58+
/* Choose a secret key such that the resulting pubkey and xonly_pubkey match. */
59+
memset(sk, 0, sizeof(sk));
60+
sk[0] = 1;
61+
CHECK(secp256k1_ec_pubkey_create(ctx, &pk, sk) == 1);
62+
CHECK(secp256k1_xonly_pubkey_from_pubkey(ctx, &xonly_pk, &pk_parity, &pk) == 1);
63+
CHECK(memcmp(&pk, &xonly_pk, sizeof(pk)) == 0);
64+
CHECK(pk_parity == 0);
65+
66+
/* Choose a secret key such that pubkey and xonly_pubkey are each others
67+
* negation. */
68+
sk[0] = 2;
69+
CHECK(secp256k1_ec_pubkey_create(ctx, &pk, sk) == 1);
70+
CHECK(secp256k1_xonly_pubkey_from_pubkey(ctx, &xonly_pk, &pk_parity, &pk) == 1);
71+
CHECK(memcmp(&xonly_pk, &pk, sizeof(xonly_pk)) != 0);
72+
CHECK(pk_parity == 1);
73+
secp256k1_pubkey_load(ctx, &pk1, &pk);
74+
secp256k1_pubkey_load(ctx, &pk2, (secp256k1_pubkey *) &xonly_pk);
75+
CHECK(secp256k1_fe_equal(&pk1.x, &pk2.x) == 1);
76+
secp256k1_fe_negate(&y, &pk2.y, 1);
77+
CHECK(secp256k1_fe_equal(&pk1.y, &y) == 1);
78+
79+
/* Test xonly_pubkey_serialize and xonly_pubkey_parse */
80+
ecount = 0;
81+
CHECK(secp256k1_xonly_pubkey_serialize(none, NULL, &xonly_pk) == 0);
82+
CHECK(ecount == 1);
83+
CHECK(secp256k1_xonly_pubkey_serialize(none, buf32, NULL) == 0);
84+
CHECK(memcmp(buf32, zeros64, 32) == 0);
85+
CHECK(ecount == 2);
86+
{
87+
/* A pubkey filled with 0s will fail to serialize due to pubkey_load
88+
* special casing. */
89+
secp256k1_xonly_pubkey pk_tmp;
90+
memset(&pk_tmp, 0, sizeof(pk_tmp));
91+
CHECK(secp256k1_xonly_pubkey_serialize(none, buf32, &pk_tmp) == 0);
92+
}
93+
/* pubkey_load called illegal callback */
94+
CHECK(ecount == 3);
95+
96+
CHECK(secp256k1_xonly_pubkey_serialize(none, buf32, &xonly_pk) == 1);
97+
ecount = 0;
98+
CHECK(secp256k1_xonly_pubkey_parse(none, NULL, buf32) == 0);
99+
CHECK(ecount == 1);
100+
CHECK(secp256k1_xonly_pubkey_parse(none, &xonly_pk, NULL) == 0);
101+
CHECK(ecount == 2);
102+
103+
/* Serialization and parse roundtrip */
104+
CHECK(secp256k1_xonly_pubkey_from_pubkey(none, &xonly_pk, NULL, &pk) == 1);
105+
CHECK(secp256k1_xonly_pubkey_serialize(ctx, buf32, &xonly_pk) == 1);
106+
CHECK(secp256k1_xonly_pubkey_parse(ctx, &xonly_pk_tmp, buf32) == 1);
107+
CHECK(memcmp(&xonly_pk, &xonly_pk_tmp, sizeof(xonly_pk)) == 0);
108+
109+
/* Test parsing invalid field elements */
110+
memset(&xonly_pk, 1, sizeof(xonly_pk));
111+
/* Overflowing field element */
112+
CHECK(secp256k1_xonly_pubkey_parse(none, &xonly_pk, ones32) == 0);
113+
CHECK(memcmp(&xonly_pk, zeros64, sizeof(xonly_pk)) == 0);
114+
memset(&xonly_pk, 1, sizeof(xonly_pk));
115+
/* There's no point with x-coordinate 0 on secp256k1 */
116+
CHECK(secp256k1_xonly_pubkey_parse(none, &xonly_pk, zeros64) == 0);
117+
CHECK(memcmp(&xonly_pk, zeros64, sizeof(xonly_pk)) == 0);
118+
/* If a random 32-byte string can not be parsed with ec_pubkey_parse
119+
* (because interpreted as X coordinate it does not correspond to a point on
120+
* the curve) then xonly_pubkey_parse should fail as well. */
121+
for (i = 0; i < count; i++) {
122+
unsigned char rand33[33];
123+
secp256k1_rand256(&rand33[1]);
124+
rand33[0] = SECP256K1_TAG_PUBKEY_EVEN;
125+
if (!secp256k1_ec_pubkey_parse(ctx, &pk, rand33, 33)) {
126+
memset(&xonly_pk, 1, sizeof(xonly_pk));
127+
CHECK(secp256k1_xonly_pubkey_parse(ctx, &xonly_pk, &rand33[1]) == 0);
128+
CHECK(memcmp(&xonly_pk, zeros64, sizeof(xonly_pk)) == 0);
129+
} else {
130+
CHECK(secp256k1_xonly_pubkey_parse(ctx, &xonly_pk, &rand33[1]) == 1);
131+
}
132+
}
133+
CHECK(ecount == 2);
134+
135+
secp256k1_context_destroy(none);
136+
secp256k1_context_destroy(sign);
137+
secp256k1_context_destroy(verify);
138+
}
139+
12140
void run_extrakeys_tests(void) {
13-
/* TODO */
141+
/* xonly key test cases */
142+
test_xonly_pubkey();
14143
}
15144

16145
#endif

0 commit comments

Comments
 (0)