Skip to content

Commit 7eeb3aa

Browse files
committed
f add xonly pubkey struct which is serialized as 32 byte and whose
Y coordinate is a quadratic residue
1 parent 2e76c5d commit 7eeb3aa

File tree

3 files changed

+214
-0
lines changed

3 files changed

+214
-0
lines changed

include/secp256k1.h

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -708,6 +708,74 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_pubkey_combine(
708708
size_t n
709709
) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
710710

711+
/** Opaque data structure that holds a parsed and valid "x-only" public key.
712+
* An x-only pubkey encodes a positive point. That is a point whose Y
713+
* coordinate is a quadratic residue. It is serialized using only its X
714+
* coordinate (32 bytes). A secp256k1_xonly_pubkey is also a secp256k1_pubkey
715+
* but the inverse is not true. Therefore, a secp256k1_pubkey must never be
716+
* interpreted as or copied into a secp256k1_xonly_pubkey.
717+
*
718+
* The exact representation of data inside is implementation defined and not
719+
* guaranteed to be portable between different platforms or versions. It is
720+
* however guaranteed to be 64 bytes in size, and can be safely copied/moved.
721+
* If you need to convert to a format suitable for storage, transmission, or
722+
* comparison, use secp256k1_xonly_pubkey_serialize and
723+
* secp256k1_xonly_pubkey_parse.
724+
*/
725+
typedef struct {
726+
unsigned char data[64];
727+
} secp256k1_xonly_pubkey;
728+
729+
/** Parse a 32-byte public key into a xonly_pubkey object.
730+
*
731+
* Returns: 1 if the public key was fully valid.
732+
* 0 if the public key could not be parsed or is invalid.
733+
*
734+
* Args: ctx: a secp256k1 context object.
735+
* Out: pubkey: pointer to a pubkey object. If 1 is returned, it is set to a
736+
* parsed version of input. If not, its value is undefined.
737+
* In: input32: pointer to a serialized xonly public key
738+
*/
739+
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_xonly_pubkey_parse(
740+
const secp256k1_context* ctx,
741+
secp256k1_xonly_pubkey* pubkey,
742+
const unsigned char *input32
743+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
744+
745+
/** Serialize a xonly pubkey object into a byte sequence.
746+
*
747+
* Returns: 1 always.
748+
*
749+
* Args: ctx: a secp256k1 context object.
750+
* Out: output32: a pointer to a 32-byte byte array to place the
751+
* serialized key in.
752+
* In: pubkey: a pointer to a secp256k1_xonly_pubkey containing an
753+
* initialized public key.
754+
*/
755+
SECP256K1_API int secp256k1_xonly_pubkey_serialize(
756+
const secp256k1_context* ctx,
757+
unsigned char *output32,
758+
const secp256k1_xonly_pubkey* pubkey
759+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
760+
761+
/** Compute the xonly public key for a secret key. Just as ec_pubkey_create this
762+
* function computes the point P by multiplying the seckey (interpreted as a scalar)
763+
* with the generator. The public key corresponds to P if the Y coordinate of P is a
764+
* quadratic residue or -P otherwise.
765+
*
766+
* Returns: 1 if secret was valid, public key stores
767+
* 0 if secret was invalid, try again
768+
*
769+
* Args: ctx: pointer to a context object, initialized for signing (cannot be NULL)
770+
* Out: pubkey: pointer to the created xonly public key (cannot be NULL)
771+
* In: seckey: pointer to a 32-byte private key (cannot be NULL)
772+
*/
773+
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_xonly_pubkey_create(
774+
const secp256k1_context* ctx,
775+
secp256k1_xonly_pubkey *pubkey,
776+
const unsigned char *seckey
777+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
778+
711779
#ifdef __cplusplus
712780
}
713781
#endif

src/secp256k1.c

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,61 @@ int secp256k1_ec_pubkey_combine(const secp256k1_context* ctx, secp256k1_pubkey *
730730
return 1;
731731
}
732732

733+
/* Converts a point into its absolute value, i.e. keeps it as is if it is
734+
* positive and otherwise negates it. */
735+
static void secp256k1_ec_pubkey_absolute(const secp256k1_context* ctx, secp256k1_pubkey *pubkey) {
736+
secp256k1_ge ge;
737+
secp256k1_pubkey_load(ctx, &ge, pubkey);
738+
if (!secp256k1_fe_is_quad_var(&ge.y)) {
739+
secp256k1_ge_neg(&ge, &ge);
740+
}
741+
secp256k1_pubkey_save(pubkey, &ge);
742+
}
743+
744+
int secp256k1_xonly_pubkey_create(const secp256k1_context* ctx, secp256k1_xonly_pubkey *pubkey, const unsigned char *seckey) {
745+
VERIFY_CHECK(ctx != NULL);
746+
ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx));
747+
ARG_CHECK(pubkey != NULL);
748+
ARG_CHECK(seckey != NULL);
749+
750+
if (!secp256k1_ec_pubkey_create(ctx, (secp256k1_pubkey *) pubkey, seckey)) {
751+
return 0;
752+
}
753+
secp256k1_ec_pubkey_absolute(ctx, (secp256k1_pubkey *) pubkey);
754+
return 1;
755+
}
756+
757+
int secp256k1_xonly_pubkey_parse(const secp256k1_context* ctx, secp256k1_xonly_pubkey* pubkey, const unsigned char *input32) {
758+
/* TODO parse directly from 32 byte buffer */
759+
unsigned char buf[33];
760+
VERIFY_CHECK(ctx != NULL);
761+
ARG_CHECK(pubkey != NULL);
762+
ARG_CHECK(input32 != NULL);
763+
764+
buf[0] = SECP256K1_TAG_PUBKEY_EVEN;
765+
memcpy(&buf[1], input32, 32);
766+
if (!secp256k1_ec_pubkey_parse(ctx, (secp256k1_pubkey *) pubkey, buf, sizeof(buf))) {
767+
return 0;
768+
}
769+
secp256k1_ec_pubkey_absolute(ctx, (secp256k1_pubkey *) pubkey);
770+
return 1;
771+
}
772+
773+
int secp256k1_xonly_pubkey_serialize(const secp256k1_context* ctx, unsigned char *output32, const secp256k1_xonly_pubkey* pubkey) {
774+
/* TODO serialize directly into 32 byte buffer */
775+
unsigned char buf[33];
776+
size_t outputlen = sizeof(buf);
777+
VERIFY_CHECK(ctx != NULL);
778+
ARG_CHECK(pubkey != NULL);
779+
ARG_CHECK(output32 != NULL);
780+
781+
if (!secp256k1_ec_pubkey_serialize(ctx, buf, &outputlen, (secp256k1_pubkey *) pubkey, SECP256K1_EC_COMPRESSED)) {
782+
return 0;
783+
}
784+
memcpy(output32, &buf[1], 32);
785+
return 1;
786+
}
787+
733788
#ifdef ENABLE_MODULE_ECDH
734789
# include "modules/ecdh/main_impl.h"
735790
#endif

src/tests.c

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4268,6 +4268,93 @@ void run_eckey_edge_case_test(void) {
42684268
secp256k1_context_set_illegal_callback(ctx, NULL, NULL);
42694269
}
42704270

4271+
void test_xonly_pubkey(void) {
4272+
unsigned char sk[32] = { 0 };
4273+
unsigned char garbage[32];
4274+
secp256k1_pubkey signed_pk;
4275+
secp256k1_xonly_pubkey xonly_pk;
4276+
secp256k1_xonly_pubkey xonly_pk_tmp;
4277+
secp256k1_ge pk1;
4278+
secp256k1_ge pk2;
4279+
secp256k1_fe y;
4280+
unsigned char buf32[32];
4281+
4282+
/* sk = 0 should fail */
4283+
CHECK(secp256k1_xonly_pubkey_create(ctx, &xonly_pk, sk) == 0);
4284+
4285+
/* Check that X coordinate of normal pubkey and x-only pubkey matches
4286+
* and that due to choice of secret key the Y coordinates are each others
4287+
* additive inverse. */
4288+
sk[0] = 6;
4289+
CHECK(secp256k1_ec_pubkey_create(ctx, &signed_pk, sk) == 1);
4290+
CHECK(secp256k1_xonly_pubkey_create(ctx, &xonly_pk, sk) == 1);
4291+
secp256k1_pubkey_load(ctx, &pk1, &signed_pk);
4292+
secp256k1_pubkey_load(ctx, &pk2, (secp256k1_pubkey *) &xonly_pk);
4293+
CHECK(secp256k1_fe_equal(&pk1.x, &pk2.x) == 1);
4294+
secp256k1_fe_negate(&y, &pk2.y, 1);
4295+
CHECK(secp256k1_fe_equal(&pk1.y, &y) == 1);
4296+
4297+
/* Serialization and parse roundtrip */
4298+
CHECK(secp256k1_xonly_pubkey_create(ctx, &xonly_pk, sk) == 1);
4299+
CHECK(secp256k1_xonly_pubkey_serialize(ctx, buf32, &xonly_pk) == 1);
4300+
CHECK(secp256k1_xonly_pubkey_parse(ctx, &xonly_pk_tmp, buf32) == 1);
4301+
CHECK(memcmp(&xonly_pk, &xonly_pk_tmp, sizeof(xonly_pk)) == 0);
4302+
4303+
/* Can't parse a byte string that's not a valid X coordinate */
4304+
memset(garbage, 0, sizeof(garbage));
4305+
CHECK(secp256k1_xonly_pubkey_parse(ctx, &xonly_pk_tmp, garbage) == 0);
4306+
}
4307+
4308+
void test_xonly_pubkey_api(void) {
4309+
secp256k1_xonly_pubkey pk;
4310+
unsigned char sk[32];
4311+
unsigned char buf32[32];
4312+
4313+
/** setup **/
4314+
secp256k1_context *none = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
4315+
secp256k1_context *sign = secp256k1_context_create(SECP256K1_CONTEXT_SIGN);
4316+
secp256k1_context *vrfy = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY);
4317+
int ecount;
4318+
4319+
secp256k1_context_set_error_callback(none, counting_illegal_callback_fn, &ecount);
4320+
secp256k1_context_set_error_callback(sign, counting_illegal_callback_fn, &ecount);
4321+
secp256k1_context_set_error_callback(vrfy, counting_illegal_callback_fn, &ecount);
4322+
secp256k1_context_set_illegal_callback(none, counting_illegal_callback_fn, &ecount);
4323+
secp256k1_context_set_illegal_callback(sign, counting_illegal_callback_fn, &ecount);
4324+
secp256k1_context_set_illegal_callback(vrfy, counting_illegal_callback_fn, &ecount);
4325+
4326+
secp256k1_rand256(sk);
4327+
ecount = 0;
4328+
CHECK(secp256k1_xonly_pubkey_create(none, &pk, sk) == 0);
4329+
CHECK(ecount == 1);
4330+
CHECK(secp256k1_xonly_pubkey_create(sign, &pk, sk) == 1);
4331+
CHECK(secp256k1_xonly_pubkey_create(vrfy, &pk, sk) == 0);
4332+
CHECK(ecount == 2);
4333+
CHECK(secp256k1_xonly_pubkey_create(sign, NULL, sk) == 0);
4334+
CHECK(ecount == 3);
4335+
CHECK(secp256k1_xonly_pubkey_create(sign, &pk, NULL) == 0);
4336+
CHECK(ecount == 4);
4337+
4338+
ecount = 0;
4339+
CHECK(secp256k1_xonly_pubkey_create(sign, &pk, sk) == 1);
4340+
CHECK(secp256k1_xonly_pubkey_serialize(none, buf32, &pk) == 1);
4341+
CHECK(secp256k1_xonly_pubkey_serialize(none, NULL, &pk) == 0);
4342+
CHECK(ecount == 1);
4343+
CHECK(secp256k1_xonly_pubkey_serialize(none, buf32, NULL) == 0);
4344+
CHECK(ecount == 2);
4345+
4346+
ecount = 0;
4347+
CHECK(secp256k1_xonly_pubkey_parse(none, &pk, buf32) == 1);
4348+
CHECK(secp256k1_xonly_pubkey_parse(none, NULL, buf32) == 0);
4349+
CHECK(ecount == 1);
4350+
CHECK(secp256k1_xonly_pubkey_parse(none, &pk, NULL) == 0);
4351+
CHECK(ecount == 2);
4352+
4353+
secp256k1_context_destroy(none);
4354+
secp256k1_context_destroy(sign);
4355+
secp256k1_context_destroy(vrfy);
4356+
}
4357+
42714358
void random_sign(secp256k1_scalar *sigr, secp256k1_scalar *sigs, const secp256k1_scalar *key, const secp256k1_scalar *msg, int *recid) {
42724359
secp256k1_scalar nonce;
42734360
do {
@@ -5438,6 +5525,10 @@ int main(int argc, char **argv) {
54385525
/* EC key edge cases */
54395526
run_eckey_edge_case_test();
54405527

5528+
/* Positive key test cases */
5529+
test_xonly_pubkey();
5530+
test_xonly_pubkey_api();
5531+
54415532
#ifdef ENABLE_MODULE_ECDH
54425533
/* ecdh tests */
54435534
run_ecdh_tests();

0 commit comments

Comments
 (0)