]> Git Repo - secp256k1.git/commitdiff
extrakeys: Add xonly_pubkey with serialize, parse and from_pubkey
authorJonas Nick <[email protected]>
Tue, 12 May 2020 14:40:28 +0000 (14:40 +0000)
committerJonas Nick <[email protected]>
Sun, 6 Sep 2020 18:59:50 +0000 (18:59 +0000)
include/secp256k1_extrakeys.h
src/modules/extrakeys/main_impl.h
src/modules/extrakeys/tests_impl.h

index e453c9dd5b12ee0fcb551ac49f861148264c4dd0..fdbfdeec33249d271da68ba98890248465c073d1 100644 (file)
@@ -7,6 +7,75 @@
 extern "C" {
 #endif
 
+/** Opaque data structure that holds a parsed and valid "x-only" public key.
+ *  An x-only pubkey encodes a point whose Y coordinate is even. It is
+ *  serialized using only its X coordinate (32 bytes). See BIP-340 for more
+ *  information about x-only pubkeys.
+ *
+ *  The exact representation of data inside is implementation defined and not
+ *  guaranteed to be portable between different platforms or versions. It is
+ *  however guaranteed to be 64 bytes in size, and can be safely copied/moved.
+ *  If you need to convert to a format suitable for storage, transmission, or
+ *  comparison, use secp256k1_xonly_pubkey_serialize and
+ *  secp256k1_xonly_pubkey_parse.
+ */
+typedef struct {
+    unsigned char data[64];
+} secp256k1_xonly_pubkey;
+
+/** Parse a 32-byte sequence into a xonly_pubkey object.
+ *
+ *  Returns: 1 if the public key was fully valid.
+ *           0 if the public key could not be parsed or is invalid.
+ *
+ *  Args:   ctx: a secp256k1 context object (cannot be NULL).
+ *  Out: pubkey: pointer to a pubkey object. If 1 is returned, it is set to a
+ *               parsed version of input. If not, it's set to an invalid value.
+ *               (cannot be NULL).
+ *  In: input32: pointer to a serialized xonly_pubkey (cannot be NULL)
+ */
+SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_xonly_pubkey_parse(
+    const secp256k1_context* ctx,
+    secp256k1_xonly_pubkey* pubkey,
+    const unsigned char *input32
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
+
+/** Serialize an xonly_pubkey object into a 32-byte sequence.
+ *
+ *  Returns: 1 always.
+ *
+ *  Args:     ctx: a secp256k1 context object (cannot be NULL).
+ *  Out: output32: a pointer to a 32-byte array to place the serialized key in
+ *                 (cannot be NULL).
+ *  In:    pubkey: a pointer to a secp256k1_xonly_pubkey containing an
+ *                 initialized public key (cannot be NULL).
+ */
+SECP256K1_API int secp256k1_xonly_pubkey_serialize(
+    const secp256k1_context* ctx,
+    unsigned char *output32,
+    const secp256k1_xonly_pubkey* pubkey
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
+
+/** Converts a secp256k1_pubkey into a secp256k1_xonly_pubkey.
+ *
+ *  Returns: 1 if the public key was successfully converted
+ *           0 otherwise
+ *
+ *  Args:         ctx: pointer to a context object (cannot be NULL)
+ *  Out: xonly_pubkey: pointer to an x-only public key object for placing the
+ *                     converted public key (cannot be NULL)
+ *          pk_parity: pointer to an integer that will be set to 1 if the point
+ *                     encoded by xonly_pubkey is the negation of the pubkey and
+ *                     set to 0 otherwise. (can be NULL)
+ *  In:        pubkey: pointer to a public key that is converted (cannot be NULL)
+ */
+SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_xonly_pubkey_from_pubkey(
+    const secp256k1_context* ctx,
+    secp256k1_xonly_pubkey *xonly_pubkey,
+    int *pk_parity,
+    const secp256k1_pubkey *pubkey
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4);
+
 #ifdef __cplusplus
 }
 #endif
index 0b2b27beeb3359a14905d003c8c22498d89b2e38..4f6fd3622d4bbd1ba69a1e93d7f5c941a7a72f2f 100644 (file)
 #include "include/secp256k1.h"
 #include "include/secp256k1_extrakeys.h"
 
+static SECP256K1_INLINE int secp256k1_xonly_pubkey_load(const secp256k1_context* ctx, secp256k1_ge *ge, const secp256k1_xonly_pubkey *pubkey) {
+    return secp256k1_pubkey_load(ctx, ge, (const secp256k1_pubkey *) pubkey);
+}
+
+static SECP256K1_INLINE void secp256k1_xonly_pubkey_save(secp256k1_xonly_pubkey *pubkey, secp256k1_ge *ge) {
+    secp256k1_pubkey_save((secp256k1_pubkey *) pubkey, ge);
+}
+
+int secp256k1_xonly_pubkey_parse(const secp256k1_context* ctx, secp256k1_xonly_pubkey *pubkey, const unsigned char *input32) {
+    secp256k1_ge pk;
+    secp256k1_fe x;
+
+    VERIFY_CHECK(ctx != NULL);
+    ARG_CHECK(pubkey != NULL);
+    memset(pubkey, 0, sizeof(*pubkey));
+    ARG_CHECK(input32 != NULL);
+
+    if (!secp256k1_fe_set_b32(&x, input32)) {
+        return 0;
+    }
+    if (!secp256k1_ge_set_xo_var(&pk, &x, 0)) {
+        return 0;
+    }
+    secp256k1_xonly_pubkey_save(pubkey, &pk);
+    return 1;
+}
+
+int secp256k1_xonly_pubkey_serialize(const secp256k1_context* ctx, unsigned char *output32, const secp256k1_xonly_pubkey *pubkey) {
+    secp256k1_ge pk;
+
+    VERIFY_CHECK(ctx != NULL);
+    ARG_CHECK(output32 != NULL);
+    memset(output32, 0, 32);
+    ARG_CHECK(pubkey != NULL);
+
+    if (!secp256k1_xonly_pubkey_load(ctx, &pk, pubkey)) {
+        return 0;
+    }
+    secp256k1_fe_get_b32(output32, &pk.x);
+    return 1;
+}
+
+/** Keeps a group element as is if it has an even Y and otherwise negates it.
+ *  y_parity is set to 0 in the former case and to 1 in the latter case.
+ *  Requires that the coordinates of r are normalized. */
+static int secp256k1_extrakeys_ge_even_y(secp256k1_ge *r) {
+    int y_parity = 0;
+    VERIFY_CHECK(!secp256k1_ge_is_infinity(r));
+
+    if (secp256k1_fe_is_odd(&r->y)) {
+        secp256k1_fe_negate(&r->y, &r->y, 1);
+        y_parity = 1;
+    }
+    return y_parity;
+}
+
+int secp256k1_xonly_pubkey_from_pubkey(const secp256k1_context* ctx, secp256k1_xonly_pubkey *xonly_pubkey, int *pk_parity, const secp256k1_pubkey *pubkey) {
+    secp256k1_ge pk;
+    int tmp;
+
+    VERIFY_CHECK(ctx != NULL);
+    ARG_CHECK(xonly_pubkey != NULL);
+    ARG_CHECK(pubkey != NULL);
+
+    if (!secp256k1_pubkey_load(ctx, &pk, pubkey)) {
+        return 0;
+    }
+    tmp = secp256k1_extrakeys_ge_even_y(&pk);
+    if (pk_parity != NULL) {
+        *pk_parity = tmp;
+    }
+    secp256k1_xonly_pubkey_save(xonly_pubkey, &pk);
+    return 1;
+}
+
 #endif
index 9d1d80e18dc5ca3cd284d1be13e82b98975a57f7..ebe51325588c9c016c3f51d590d8ca02920b025d 100644 (file)
@@ -9,8 +9,137 @@
 
 #include "secp256k1_extrakeys.h"
 
+static secp256k1_context* api_test_context(int flags, int *ecount) {
+    secp256k1_context *ctx0 = secp256k1_context_create(flags);
+    secp256k1_context_set_error_callback(ctx0, counting_illegal_callback_fn, ecount);
+    secp256k1_context_set_illegal_callback(ctx0, counting_illegal_callback_fn, ecount);
+    return ctx0;
+}
+
+void test_xonly_pubkey(void) {
+    secp256k1_pubkey pk;
+    secp256k1_xonly_pubkey xonly_pk, xonly_pk_tmp;
+    secp256k1_ge pk1;
+    secp256k1_ge pk2;
+    secp256k1_fe y;
+    unsigned char sk[32];
+    unsigned char xy_sk[32];
+    unsigned char buf32[32];
+    unsigned char ones32[32];
+    unsigned char zeros64[64] = { 0 };
+    int pk_parity;
+    int i;
+
+    int ecount;
+    secp256k1_context *none = api_test_context(SECP256K1_CONTEXT_NONE, &ecount);
+    secp256k1_context *sign = api_test_context(SECP256K1_CONTEXT_SIGN, &ecount);
+    secp256k1_context *verify = api_test_context(SECP256K1_CONTEXT_VERIFY, &ecount);
+
+    secp256k1_rand256(sk);
+    memset(ones32, 0xFF, 32);
+    secp256k1_rand256(xy_sk);
+    CHECK(secp256k1_ec_pubkey_create(sign, &pk, sk) == 1);
+    CHECK(secp256k1_xonly_pubkey_from_pubkey(none, &xonly_pk, &pk_parity, &pk) == 1);
+
+    /* Test xonly_pubkey_from_pubkey */
+    ecount = 0;
+    CHECK(secp256k1_xonly_pubkey_from_pubkey(none, &xonly_pk, &pk_parity, &pk) == 1);
+    CHECK(secp256k1_xonly_pubkey_from_pubkey(sign, &xonly_pk, &pk_parity, &pk) == 1);
+    CHECK(secp256k1_xonly_pubkey_from_pubkey(verify, &xonly_pk, &pk_parity, &pk) == 1);
+    CHECK(secp256k1_xonly_pubkey_from_pubkey(none, NULL, &pk_parity, &pk) == 0);
+    CHECK(ecount == 1);
+    CHECK(secp256k1_xonly_pubkey_from_pubkey(none, &xonly_pk, NULL, &pk) == 1);
+    CHECK(secp256k1_xonly_pubkey_from_pubkey(none, &xonly_pk, &pk_parity, NULL) == 0);
+    CHECK(ecount == 2);
+    memset(&pk, 0, sizeof(pk));
+    CHECK(secp256k1_xonly_pubkey_from_pubkey(none, &xonly_pk, &pk_parity, &pk) == 0);
+    CHECK(ecount == 3);
+
+    /* Choose a secret key such that the resulting pubkey and xonly_pubkey match. */
+    memset(sk, 0, sizeof(sk));
+    sk[0] = 1;
+    CHECK(secp256k1_ec_pubkey_create(ctx, &pk, sk) == 1);
+    CHECK(secp256k1_xonly_pubkey_from_pubkey(ctx, &xonly_pk, &pk_parity, &pk) == 1);
+    CHECK(memcmp(&pk, &xonly_pk, sizeof(pk)) == 0);
+    CHECK(pk_parity == 0);
+
+    /* Choose a secret key such that pubkey and xonly_pubkey are each others
+     * negation. */
+    sk[0] = 2;
+    CHECK(secp256k1_ec_pubkey_create(ctx, &pk, sk) == 1);
+    CHECK(secp256k1_xonly_pubkey_from_pubkey(ctx, &xonly_pk, &pk_parity, &pk) == 1);
+    CHECK(memcmp(&xonly_pk, &pk, sizeof(xonly_pk)) != 0);
+    CHECK(pk_parity == 1);
+    secp256k1_pubkey_load(ctx, &pk1, &pk);
+    secp256k1_pubkey_load(ctx, &pk2, (secp256k1_pubkey *) &xonly_pk);
+    CHECK(secp256k1_fe_equal(&pk1.x, &pk2.x) == 1);
+    secp256k1_fe_negate(&y, &pk2.y, 1);
+    CHECK(secp256k1_fe_equal(&pk1.y, &y) == 1);
+
+    /* Test xonly_pubkey_serialize and xonly_pubkey_parse */
+    ecount = 0;
+    CHECK(secp256k1_xonly_pubkey_serialize(none, NULL, &xonly_pk) == 0);
+    CHECK(ecount == 1);
+    CHECK(secp256k1_xonly_pubkey_serialize(none, buf32, NULL) == 0);
+    CHECK(memcmp(buf32, zeros64, 32) == 0);
+    CHECK(ecount == 2);
+    {
+        /* A pubkey filled with 0s will fail to serialize due to pubkey_load
+         * special casing. */
+        secp256k1_xonly_pubkey pk_tmp;
+        memset(&pk_tmp, 0, sizeof(pk_tmp));
+        CHECK(secp256k1_xonly_pubkey_serialize(none, buf32, &pk_tmp) == 0);
+    }
+    /* pubkey_load called illegal callback */
+    CHECK(ecount == 3);
+
+    CHECK(secp256k1_xonly_pubkey_serialize(none, buf32, &xonly_pk) == 1);
+    ecount = 0;
+    CHECK(secp256k1_xonly_pubkey_parse(none, NULL, buf32) == 0);
+    CHECK(ecount == 1);
+    CHECK(secp256k1_xonly_pubkey_parse(none, &xonly_pk, NULL) == 0);
+    CHECK(ecount == 2);
+
+    /* Serialization and parse roundtrip */
+    CHECK(secp256k1_xonly_pubkey_from_pubkey(none, &xonly_pk, NULL, &pk) == 1);
+    CHECK(secp256k1_xonly_pubkey_serialize(ctx, buf32, &xonly_pk) == 1);
+    CHECK(secp256k1_xonly_pubkey_parse(ctx, &xonly_pk_tmp, buf32) == 1);
+    CHECK(memcmp(&xonly_pk, &xonly_pk_tmp, sizeof(xonly_pk)) == 0);
+
+    /* Test parsing invalid field elements */
+    memset(&xonly_pk, 1, sizeof(xonly_pk));
+    /* Overflowing field element */
+    CHECK(secp256k1_xonly_pubkey_parse(none, &xonly_pk, ones32) == 0);
+    CHECK(memcmp(&xonly_pk, zeros64, sizeof(xonly_pk)) == 0);
+    memset(&xonly_pk, 1, sizeof(xonly_pk));
+    /* There's no point with x-coordinate 0 on secp256k1 */
+    CHECK(secp256k1_xonly_pubkey_parse(none, &xonly_pk, zeros64) == 0);
+    CHECK(memcmp(&xonly_pk, zeros64, sizeof(xonly_pk)) == 0);
+    /* If a random 32-byte string can not be parsed with ec_pubkey_parse
+     * (because interpreted as X coordinate it does not correspond to a point on
+     * the curve) then xonly_pubkey_parse should fail as well. */
+    for (i = 0; i < count; i++) {
+        unsigned char rand33[33];
+        secp256k1_rand256(&rand33[1]);
+        rand33[0] = SECP256K1_TAG_PUBKEY_EVEN;
+        if (!secp256k1_ec_pubkey_parse(ctx, &pk, rand33, 33)) {
+            memset(&xonly_pk, 1, sizeof(xonly_pk));
+            CHECK(secp256k1_xonly_pubkey_parse(ctx, &xonly_pk, &rand33[1]) == 0);
+            CHECK(memcmp(&xonly_pk, zeros64, sizeof(xonly_pk)) == 0);
+        } else {
+            CHECK(secp256k1_xonly_pubkey_parse(ctx, &xonly_pk, &rand33[1]) == 1);
+        }
+    }
+    CHECK(ecount == 2);
+
+    secp256k1_context_destroy(none);
+    secp256k1_context_destroy(sign);
+    secp256k1_context_destroy(verify);
+}
+
 void run_extrakeys_tests(void) {
-    /* TODO */
+    /* xonly key test cases */
+    test_xonly_pubkey();
 }
 
 #endif
This page took 0.036829 seconds and 4 git commands to generate.