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