Skip to content

Commit

Permalink
extrakeys: Add xonly_pubkey_tweak_add & xonly_pubkey_tweak_add_test
Browse files Browse the repository at this point in the history
  • Loading branch information
jonasnick committed Sep 6, 2020
1 parent 176bfb1 commit 910d9c2
Show file tree
Hide file tree
Showing 3 changed files with 279 additions and 0 deletions.
64 changes: 64 additions & 0 deletions include/secp256k1_extrakeys.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,70 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_xonly_pubkey_from_pubke
const secp256k1_pubkey *pubkey
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4);

/** Tweak an x-only public key by adding the generator multiplied with tweak32
* to it.
*
* Note that the resulting point can not in general be represented by an x-only
* pubkey because it may have an odd Y coordinate. Instead, the output_pubkey
* is a normal secp256k1_pubkey.
*
* Returns: 0 if the arguments are invalid or the resulting public key would be
* invalid (only when the tweak is the negation of the corresponding
* secret key). 1 otherwise.
*
* Args: ctx: pointer to a context object initialized for verification
* (cannot be NULL)
* Out: output_pubkey: pointer to a public key to store the result. Will be set
* to an invalid value if this function returns 0 (cannot
* be NULL)
* In: internal_pubkey: pointer to an x-only pubkey to apply the tweak to.
* (cannot be NULL).
* tweak32: pointer to a 32-byte tweak. If the tweak is invalid
* according to secp256k1_ec_seckey_verify, this function
* returns 0. For uniformly random 32-byte arrays the
* chance of being invalid is negligible (around 1 in
* 2^128) (cannot be NULL).
*/
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_xonly_pubkey_tweak_add(
const secp256k1_context* ctx,
secp256k1_pubkey *output_pubkey,
const secp256k1_xonly_pubkey *internal_pubkey,
const unsigned char *tweak32
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);

/** Checks that a tweaked pubkey is the result of calling
* secp256k1_xonly_pubkey_tweak_add with internal_pubkey and tweak32.
*
* The tweaked pubkey is represented by its 32-byte x-only serialization and
* its pk_parity, which can both be obtained by converting the result of
* tweak_add to a secp256k1_xonly_pubkey.
*
* Note that this alone does _not_ verify that the tweaked pubkey is a
* commitment. If the tweak is not chosen in a specific way, the tweaked pubkey
* can easily be the result of a different internal_pubkey and tweak.
*
* Returns: 0 if the arguments are invalid or the tweaked pubkey is not the
* result of tweaking the internal_pubkey with tweak32. 1 otherwise.
* Args: ctx: pointer to a context object initialized for verification
* (cannot be NULL)
* In: tweaked_pubkey32: pointer to a serialized xonly_pubkey (cannot be NULL)
* tweaked_pk_parity: the parity of the tweaked pubkey (whose serialization
* is passed in as tweaked_pubkey32). This must match the
* pk_parity value that is returned when calling
* secp256k1_xonly_pubkey with the tweaked pubkey, or
* this function will fail.
* internal_pubkey: pointer to an x-only public key object to apply the
* tweak to (cannot be NULL)
* tweak32: pointer to a 32-byte tweak (cannot be NULL)
*/
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_xonly_pubkey_tweak_add_check(
const secp256k1_context* ctx,
const unsigned char *tweaked_pubkey32,
int tweaked_pk_parity,
const secp256k1_xonly_pubkey *internal_pubkey,
const unsigned char *tweak32
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5);

#ifdef __cplusplus
}
#endif
Expand Down
40 changes: 40 additions & 0 deletions src/modules/extrakeys/main_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,44 @@ int secp256k1_xonly_pubkey_from_pubkey(const secp256k1_context* ctx, secp256k1_x
return 1;
}

int secp256k1_xonly_pubkey_tweak_add(const secp256k1_context* ctx, secp256k1_pubkey *output_pubkey, const secp256k1_xonly_pubkey *internal_pubkey, const unsigned char *tweak32) {
secp256k1_ge pk;

VERIFY_CHECK(ctx != NULL);
ARG_CHECK(output_pubkey != NULL);
memset(output_pubkey, 0, sizeof(*output_pubkey));
ARG_CHECK(secp256k1_ecmult_context_is_built(&ctx->ecmult_ctx));
ARG_CHECK(internal_pubkey != NULL);
ARG_CHECK(tweak32 != NULL);

if (!secp256k1_xonly_pubkey_load(ctx, &pk, internal_pubkey)
|| !secp256k1_ec_pubkey_tweak_add_helper(&ctx->ecmult_ctx, &pk, tweak32)) {
return 0;
}
secp256k1_pubkey_save(output_pubkey, &pk);
return 1;
}

int secp256k1_xonly_pubkey_tweak_add_check(const secp256k1_context* ctx, const unsigned char *tweaked_pubkey32, int tweaked_pk_parity, const secp256k1_xonly_pubkey *internal_pubkey, const unsigned char *tweak32) {
secp256k1_ge pk;
unsigned char pk_expected32[32];

VERIFY_CHECK(ctx != NULL);
ARG_CHECK(secp256k1_ecmult_context_is_built(&ctx->ecmult_ctx));
ARG_CHECK(internal_pubkey != NULL);
ARG_CHECK(tweaked_pubkey32 != NULL);
ARG_CHECK(tweak32 != NULL);

if (!secp256k1_xonly_pubkey_load(ctx, &pk, internal_pubkey)
|| !secp256k1_ec_pubkey_tweak_add_helper(&ctx->ecmult_ctx, &pk, tweak32)) {
return 0;
}
secp256k1_fe_normalize_var(&pk.x);
secp256k1_fe_normalize_var(&pk.y);
secp256k1_fe_get_b32(pk_expected32, &pk.x);

return memcmp(&pk_expected32, tweaked_pubkey32, 32) == 0
&& secp256k1_fe_is_odd(&pk.y) == tweaked_pk_parity;
}

#endif
175 changes: 175 additions & 0 deletions src/modules/extrakeys/tests_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,184 @@ void test_xonly_pubkey(void) {
secp256k1_context_destroy(verify);
}

void test_xonly_pubkey_tweak(void) {
unsigned char zeros64[64] = { 0 };
unsigned char overflows[32];
unsigned char sk[32];
secp256k1_pubkey internal_pk;
secp256k1_xonly_pubkey internal_xonly_pk;
secp256k1_pubkey output_pk;
int pk_parity;
unsigned char tweak[32];
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);

memset(overflows, 0xff, sizeof(overflows));
secp256k1_rand256(tweak);
secp256k1_rand256(sk);
CHECK(secp256k1_ec_pubkey_create(ctx, &internal_pk, sk) == 1);
CHECK(secp256k1_xonly_pubkey_from_pubkey(none, &internal_xonly_pk, &pk_parity, &internal_pk) == 1);

ecount = 0;
CHECK(secp256k1_xonly_pubkey_tweak_add(none, &output_pk, &internal_xonly_pk, tweak) == 0);
CHECK(ecount == 1);
CHECK(secp256k1_xonly_pubkey_tweak_add(sign, &output_pk, &internal_xonly_pk, tweak) == 0);
CHECK(ecount == 2);
CHECK(secp256k1_xonly_pubkey_tweak_add(verify, &output_pk, &internal_xonly_pk, tweak) == 1);
CHECK(secp256k1_xonly_pubkey_tweak_add(verify, NULL, &internal_xonly_pk, tweak) == 0);
CHECK(ecount == 3);
CHECK(secp256k1_xonly_pubkey_tweak_add(verify, &output_pk, NULL, tweak) == 0);
CHECK(ecount == 4);
/* NULL internal_xonly_pk zeroes the output_pk */
CHECK(memcmp(&output_pk, zeros64, sizeof(output_pk)) == 0);
CHECK(secp256k1_xonly_pubkey_tweak_add(verify, &output_pk, &internal_xonly_pk, NULL) == 0);
CHECK(ecount == 5);
/* NULL tweak zeroes the output_pk */
CHECK(memcmp(&output_pk, zeros64, sizeof(output_pk)) == 0);

/* Invalid tweak zeroes the output_pk */
CHECK(secp256k1_xonly_pubkey_tweak_add(verify, &output_pk, &internal_xonly_pk, overflows) == 0);
CHECK(memcmp(&output_pk, zeros64, sizeof(output_pk)) == 0);

/* A zero tweak is fine */
CHECK(secp256k1_xonly_pubkey_tweak_add(verify, &output_pk, &internal_xonly_pk, zeros64) == 1);

/* Fails if the resulting key was infinity */
for (i = 0; i < count; i++) {
secp256k1_scalar scalar_tweak;
/* Because sk may be negated before adding, we need to try with tweak =
* sk as well as tweak = -sk. */
secp256k1_scalar_set_b32(&scalar_tweak, sk, NULL);
secp256k1_scalar_negate(&scalar_tweak, &scalar_tweak);
secp256k1_scalar_get_b32(tweak, &scalar_tweak);
CHECK((secp256k1_xonly_pubkey_tweak_add(verify, &output_pk, &internal_xonly_pk, sk) == 0)
|| (secp256k1_xonly_pubkey_tweak_add(verify, &output_pk, &internal_xonly_pk, tweak) == 0));
CHECK(memcmp(&output_pk, zeros64, sizeof(output_pk)) == 0);
}

/* Invalid pk with a valid tweak */
memset(&internal_xonly_pk, 0, sizeof(internal_xonly_pk));
secp256k1_rand256(tweak);
ecount = 0;
CHECK(secp256k1_xonly_pubkey_tweak_add(verify, &output_pk, &internal_xonly_pk, tweak) == 0);
CHECK(ecount == 1);
CHECK(memcmp(&output_pk, zeros64, sizeof(output_pk)) == 0);

secp256k1_context_destroy(none);
secp256k1_context_destroy(sign);
secp256k1_context_destroy(verify);
}

void test_xonly_pubkey_tweak_check(void) {
unsigned char zeros64[64] = { 0 };
unsigned char overflows[32];
unsigned char sk[32];
secp256k1_pubkey internal_pk;
secp256k1_xonly_pubkey internal_xonly_pk;
secp256k1_pubkey output_pk;
secp256k1_xonly_pubkey output_xonly_pk;
unsigned char output_pk32[32];
unsigned char buf32[32];
int pk_parity;
unsigned char tweak[32];

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

memset(overflows, 0xff, sizeof(overflows));
secp256k1_rand256(tweak);
secp256k1_rand256(sk);
CHECK(secp256k1_ec_pubkey_create(ctx, &internal_pk, sk) == 1);
CHECK(secp256k1_xonly_pubkey_from_pubkey(none, &internal_xonly_pk, &pk_parity, &internal_pk) == 1);

ecount = 0;
CHECK(secp256k1_xonly_pubkey_tweak_add(verify, &output_pk, &internal_xonly_pk, tweak) == 1);
CHECK(secp256k1_xonly_pubkey_from_pubkey(verify, &output_xonly_pk, &pk_parity, &output_pk) == 1);
CHECK(secp256k1_xonly_pubkey_serialize(ctx, buf32, &output_xonly_pk) == 1);
CHECK(secp256k1_xonly_pubkey_tweak_add_check(none, buf32, pk_parity, &internal_xonly_pk, tweak) == 0);
CHECK(ecount == 1);
CHECK(secp256k1_xonly_pubkey_tweak_add_check(sign, buf32, pk_parity, &internal_xonly_pk, tweak) == 0);
CHECK(ecount == 2);
CHECK(secp256k1_xonly_pubkey_tweak_add_check(verify, buf32, pk_parity, &internal_xonly_pk, tweak) == 1);
CHECK(secp256k1_xonly_pubkey_tweak_add_check(verify, NULL, pk_parity, &internal_xonly_pk, tweak) == 0);
CHECK(ecount == 3);
/* invalid pk_parity value */
CHECK(secp256k1_xonly_pubkey_tweak_add_check(verify, buf32, 2, &internal_xonly_pk, tweak) == 0);
CHECK(ecount == 3);
CHECK(secp256k1_xonly_pubkey_tweak_add_check(verify, buf32, pk_parity, NULL, tweak) == 0);
CHECK(ecount == 4);
CHECK(secp256k1_xonly_pubkey_tweak_add_check(verify, buf32, pk_parity, &internal_xonly_pk, NULL) == 0);
CHECK(ecount == 5);

memset(tweak, 1, sizeof(tweak));
CHECK(secp256k1_xonly_pubkey_from_pubkey(ctx, &internal_xonly_pk, NULL, &internal_pk) == 1);
CHECK(secp256k1_xonly_pubkey_tweak_add(ctx, &output_pk, &internal_xonly_pk, tweak) == 1);
CHECK(secp256k1_xonly_pubkey_from_pubkey(ctx, &output_xonly_pk, &pk_parity, &output_pk) == 1);
CHECK(secp256k1_xonly_pubkey_serialize(ctx, output_pk32, &output_xonly_pk) == 1);
CHECK(secp256k1_xonly_pubkey_tweak_add_check(ctx, output_pk32, pk_parity, &internal_xonly_pk, tweak) == 1);

/* Wrong pk_parity */
CHECK(secp256k1_xonly_pubkey_tweak_add_check(ctx, output_pk32, !pk_parity, &internal_xonly_pk, tweak) == 0);
/* Wrong public key */
CHECK(secp256k1_xonly_pubkey_serialize(ctx, buf32, &internal_xonly_pk) == 1);
CHECK(secp256k1_xonly_pubkey_tweak_add_check(ctx, buf32, pk_parity, &internal_xonly_pk, tweak) == 0);

/* Overflowing tweak not allowed */
CHECK(secp256k1_xonly_pubkey_tweak_add_check(ctx, output_pk32, pk_parity, &internal_xonly_pk, overflows) == 0);
CHECK(secp256k1_xonly_pubkey_tweak_add(ctx, &output_pk, &internal_xonly_pk, overflows) == 0);
CHECK(memcmp(&output_pk, zeros64, sizeof(output_pk)) == 0);
CHECK(ecount == 5);

secp256k1_context_destroy(none);
secp256k1_context_destroy(sign);
secp256k1_context_destroy(verify);
}

/* Starts with an initial pubkey and recursively creates N_PUBKEYS - 1
* additional pubkeys by calling tweak_add. Then verifies every tweak starting
* from the last pubkey. */
#define N_PUBKEYS 32
void test_xonly_pubkey_tweak_recursive(void) {
unsigned char sk[32];
secp256k1_pubkey pk[N_PUBKEYS];
unsigned char pk_serialized[32];
unsigned char tweak[N_PUBKEYS - 1][32];
int i;

secp256k1_rand256(sk);
CHECK(secp256k1_ec_pubkey_create(ctx, &pk[0], sk) == 1);
/* Add tweaks */
for (i = 0; i < N_PUBKEYS - 1; i++) {
secp256k1_xonly_pubkey xonly_pk;
memset(tweak[i], i + 1, sizeof(tweak[i]));
CHECK(secp256k1_xonly_pubkey_from_pubkey(ctx, &xonly_pk, NULL, &pk[i]) == 1);
CHECK(secp256k1_xonly_pubkey_tweak_add(ctx, &pk[i + 1], &xonly_pk, tweak[i]) == 1);
}

/* Verify tweaks */
for (i = N_PUBKEYS - 1; i > 0; i--) {
secp256k1_xonly_pubkey xonly_pk;
int pk_parity;
CHECK(secp256k1_xonly_pubkey_from_pubkey(ctx, &xonly_pk, &pk_parity, &pk[i]) == 1);
CHECK(secp256k1_xonly_pubkey_serialize(ctx, pk_serialized, &xonly_pk) == 1);
CHECK(secp256k1_xonly_pubkey_from_pubkey(ctx, &xonly_pk, NULL, &pk[i - 1]) == 1);
CHECK(secp256k1_xonly_pubkey_tweak_add_check(ctx, pk_serialized, pk_parity, &xonly_pk, tweak[i - 1]) == 1);
}
}
#undef N_PUBKEYS

void run_extrakeys_tests(void) {
/* xonly key test cases */
test_xonly_pubkey();
test_xonly_pubkey_tweak();
test_xonly_pubkey_tweak_check();
test_xonly_pubkey_tweak_recursive();
}

#endif

0 comments on commit 910d9c2

Please sign in to comment.