diff --git a/include/secp256k1_frost.h b/include/secp256k1_frost.h index 77a3f252..4076e1a9 100644 --- a/include/secp256k1_frost.h +++ b/include/secp256k1_frost.h @@ -16,6 +16,9 @@ extern "C" { * Threshold Signatures (FROST) by Chelsea Komlo and Ian Goldberg * (https://crysp.uwaterloo.ca/software/frost/). * + * The module also supports BIP-341 ("Taproot") and BIP-32 ("ordinary") public + * key tweaking, and adaptor signatures. + * * Following the convention used in the MuSig module, the API uses the singular * term "nonce" to refer to the two "nonces" used by the FROST scheme. */ @@ -28,6 +31,15 @@ extern "C" { * comparison, use the corresponding serialization and parsing functions. */ +/** Opaque data structure that caches information about key tweaking. + * + * Guaranteed to be 101 bytes in size. It can be safely copied/moved. No + * serialization and parsing functions. + */ +typedef struct { + unsigned char data[101]; +} secp256k1_frost_tweak_cache; + /** Opaque data structure that holds a signer's _secret_ share. * * Guaranteed to be 36 bytes in size. Serialized and parsed with @@ -153,6 +165,123 @@ SECP256K1_API int secp256k1_frost_shares_trusted_gen( size_t n_participants ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5); +/** Obtain the aggregate public key from a FROST x-only aggregate public key. + * + * This is only useful if you need the non-xonly public key, in particular for + * ordinary (non-xonly) tweaking or batch-verifying multiple key aggregations + * (not implemented). + * + * Returns: 0 if the arguments are invalid, 1 otherwise + * Args: ctx: pointer to a context object + * Out: ec_agg_pk: the FROST-aggregated public key. + * In: xonly_agg_pk: the aggregated x-only public key that is the output of + * `secp256k1_frost_shares_gen` + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_pubkey_get( + const secp256k1_context *ctx, + secp256k1_pubkey *ec_agg_pk, + const secp256k1_xonly_pubkey *xonly_agg_pk +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + +/** Initializes a tweak cache used for applying tweaks to a FROST key + * + * Returns: 0 if the arguments are invalid, 1 otherwise + * Args: ctx: pointer to a context object + * Out: tweak_cache: pointer to a frost_tweak_cache struct that is required + * for key tweaking + * In: agg_pk: the aggregated x-only public key that is the output of + * `secp256k1_frost_shares_gen` + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_pubkey_tweak( + const secp256k1_context *ctx, + secp256k1_frost_tweak_cache *tweak_cache, + const secp256k1_xonly_pubkey *agg_pk +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + +/** Apply ordinary "EC" tweaking to a public key in a given tweak_cache by + * adding the generator multiplied with `tweak32` to it. This is useful for + * deriving child keys from an aggregate public key via BIP32. + * + * The tweaking method is the same as `secp256k1_ec_pubkey_tweak_add`. So after + * the following pseudocode buf and buf2 have identical contents (absent + * earlier failures). + * + * secp256k1_frost_shares_gen(..., xonly_agg_pk, ...) + * secp256k1_frost_pubkey_tweak(..., tweak_cache, xonly_agg_pk) + * secp256k1_frost_pubkey_ec_tweak_add(..., output_pk, tweak_cache, tweak32) + * secp256k1_ec_pubkey_serialize(..., buf, output_pk) + * secp256k1_frost_pubkey_get(..., ec_agg_pk, xonly_agg_pk) + * secp256k1_ec_pubkey_tweak_add(..., ec_agg_pk, tweak32) + * secp256k1_ec_pubkey_serialize(..., buf2, ec_agg_pk) + * + * This function is required if you want to _sign_ for a tweaked aggregate key. + * On the other hand, if you are only computing a public key, but not intending + * to create a signature for it, you can just use + * `secp256k1_ec_pubkey_tweak_add`. + * + * 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 + * Out: output_pubkey: pointer to a public key to store the result. Will be set + * to an invalid value if this function returns 0. If you + * do not need it, this arg can be NULL. + * In/Out: tweak_cache: pointer to a `frost_tweak_cache` struct initialized by + * `frost_pubkey_tweak` + * In: 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). + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_pubkey_ec_tweak_add( + const secp256k1_context *ctx, + secp256k1_pubkey *output_pubkey, + secp256k1_frost_tweak_cache *tweak_cache, + const unsigned char *tweak32 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + +/** Apply x-only tweaking to a public key in a given tweak_cache by adding the + * generator multiplied with `tweak32` to it. This is useful for creating + * Taproot outputs. + * + * The tweaking method is the same as `secp256k1_xonly_pubkey_tweak_add`. So in + * the following pseudocode xonly_pubkey_tweak_add_check (absent earlier + * failures) returns 1. + * + * secp256k1_frost_shares_gen(..., agg_pk, ...) + * secp256k1_frost_pubkey_tweak(..., tweak_cache, agg_pk) + * secp256k1_frost_pubkey_xonly_tweak_add(..., output_pk, tweak_cache, tweak32) + * secp256k1_xonly_pubkey_serialize(..., buf, output_pk) + * secp256k1_xonly_pubkey_tweak_add_check(..., buf, ..., agg_pk, tweak32) + * + * This function is required if you want to _sign_ for a tweaked aggregate key. + * On the other hand, if you are only computing a public key, but not intending + * to create a signature for it, you can just use + * `secp256k1_xonly_pubkey_tweak_add`. + * + * 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 + * Out: output_pubkey: pointer to a public key to store the result. Will be set + * to an invalid value if this function returns 0. If you + * do not need it, this arg can be NULL. + * In/Out: tweak_cache: pointer to a `frost_tweak_cache` struct initialized by + * `frost_pubkey_tweak` + * In: 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). + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_pubkey_xonly_tweak_add( + const secp256k1_context *ctx, + secp256k1_pubkey *output_pubkey, + secp256k1_frost_tweak_cache *tweak_cache, + const unsigned char *tweak32 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + /** Starts a signing session by generating a nonce * * This function outputs a secret nonce that will be required for signing and a diff --git a/src/modules/frost/keygen.h b/src/modules/frost/keygen.h index 48076567..285a8a86 100644 --- a/src/modules/frost/keygen.h +++ b/src/modules/frost/keygen.h @@ -10,8 +10,15 @@ #include "../../../include/secp256k1.h" #include "../../../include/secp256k1_frost.h" +#include "../../group.h" #include "../../scalar.h" +typedef struct { + secp256k1_ge pk; + secp256k1_scalar tweak; + int parity_acc; +} secp256k1_tweak_cache_internal; + static int secp256k1_frost_share_load(const secp256k1_context* ctx, secp256k1_scalar *s, const secp256k1_frost_share* share); #endif diff --git a/src/modules/frost/keygen_impl.h b/src/modules/frost/keygen_impl.h index d9e904bc..8c01da1b 100644 --- a/src/modules/frost/keygen_impl.h +++ b/src/modules/frost/keygen_impl.h @@ -20,6 +20,39 @@ #include "../../hash.h" #include "../../scalar.h" +static const unsigned char secp256k1_frost_tweak_cache_magic[4] = { 0x40, 0x25, 0x2e, 0x41 }; + +/* A tweak cache consists of + * - 4 byte magic set during initialization to allow detecting an uninitialized + * object. + * - 64 byte aggregate (and potentially tweaked) public key + * - 1 byte the parity of the internal key (if tweaked, otherwise 0) + * - 32 byte tweak + */ +/* Requires that cache_i->pk is not infinity. */ +static void secp256k1_tweak_cache_save(secp256k1_frost_tweak_cache *cache, secp256k1_tweak_cache_internal *cache_i) { + unsigned char *ptr = cache->data; + memcpy(ptr, secp256k1_frost_tweak_cache_magic, 4); + ptr += 4; + secp256k1_point_save_ext(ptr, &cache_i->pk); + ptr += 64; + *ptr = cache_i->parity_acc; + ptr += 1; + secp256k1_scalar_get_b32(ptr, &cache_i->tweak); +} + +static int secp256k1_tweak_cache_load(const secp256k1_context* ctx, secp256k1_tweak_cache_internal *cache_i, const secp256k1_frost_tweak_cache *cache) { + const unsigned char *ptr = cache->data; + ARG_CHECK(secp256k1_memcmp_var(ptr, secp256k1_frost_tweak_cache_magic, 4) == 0); + ptr += 4; + secp256k1_point_load_ext(&cache_i->pk, ptr); + ptr += 64; + cache_i->parity_acc = *ptr & 1; + ptr += 1; + secp256k1_scalar_set_b32(&cache_i->tweak, ptr, NULL); + return 1; +} + static const unsigned char secp256k1_frost_share_magic[4] = { 0xa1, 0x6a, 0x42, 0x03 }; static void secp256k1_frost_share_save(secp256k1_frost_share* share, secp256k1_scalar *s) { @@ -143,4 +176,82 @@ int secp256k1_frost_shares_trusted_gen(const secp256k1_context *ctx, secp256k1_f return ret; } +int secp256k1_frost_pubkey_get(const secp256k1_context* ctx, secp256k1_pubkey *ec_pk, const secp256k1_xonly_pubkey *xonly_pk) { + secp256k1_ge pk; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(ec_pk != NULL); + memset(ec_pk, 0, sizeof(*ec_pk)); + ARG_CHECK(xonly_pk != NULL); + + /* The output of keygen is an aggregated public key that *always* has an + * even Y coordinate. */ + if (!secp256k1_xonly_pubkey_load(ctx, &pk, xonly_pk)) { + return 0; + } + secp256k1_pubkey_save(ec_pk, &pk); + return 1; +} + +int secp256k1_frost_pubkey_tweak(const secp256k1_context* ctx, secp256k1_frost_tweak_cache *tweak_cache, const secp256k1_xonly_pubkey *pk) { + secp256k1_tweak_cache_internal cache_i = { 0 }; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(tweak_cache != NULL); + ARG_CHECK(pk != NULL); + + /* The output of keygen is an aggregated public key that *always* has an + * even Y coordinate. */ + if (!secp256k1_xonly_pubkey_load(ctx, &cache_i.pk, pk)) { + return 0; + } + secp256k1_tweak_cache_save(tweak_cache, &cache_i); + + return 1; +} + +static int secp256k1_frost_pubkey_tweak_add_internal(const secp256k1_context* ctx, secp256k1_pubkey *output_pubkey, secp256k1_frost_tweak_cache *tweak_cache, const unsigned char *tweak32, int xonly) { + secp256k1_tweak_cache_internal cache_i; + int overflow = 0; + secp256k1_scalar tweak; + + VERIFY_CHECK(ctx != NULL); + if (output_pubkey != NULL) { + memset(output_pubkey, 0, sizeof(*output_pubkey)); + } + ARG_CHECK(tweak_cache != NULL); + ARG_CHECK(tweak32 != NULL); + + if (!secp256k1_tweak_cache_load(ctx, &cache_i, tweak_cache)) { + return 0; + } + secp256k1_scalar_set_b32(&tweak, tweak32, &overflow); + if (overflow) { + return 0; + } + if (xonly && secp256k1_extrakeys_ge_even_y(&cache_i.pk)) { + cache_i.parity_acc ^= 1; + secp256k1_scalar_negate(&cache_i.tweak, &cache_i.tweak); + } + secp256k1_scalar_add(&cache_i.tweak, &cache_i.tweak, &tweak); + if (!secp256k1_eckey_pubkey_tweak_add(&cache_i.pk, &tweak)) { + return 0; + } + /* eckey_pubkey_tweak_add fails if cache_i.pk is infinity */ + VERIFY_CHECK(!secp256k1_ge_is_infinity(&cache_i.pk)); + secp256k1_tweak_cache_save(tweak_cache, &cache_i); + if (output_pubkey != NULL) { + secp256k1_pubkey_save(output_pubkey, &cache_i.pk); + } + return 1; +} + +int secp256k1_frost_pubkey_ec_tweak_add(const secp256k1_context* ctx, secp256k1_pubkey *output_pubkey, secp256k1_frost_tweak_cache *tweak_cache, const unsigned char *tweak32) { + return secp256k1_frost_pubkey_tweak_add_internal(ctx, output_pubkey, tweak_cache, tweak32, 0); +} + +int secp256k1_frost_pubkey_xonly_tweak_add(const secp256k1_context* ctx, secp256k1_pubkey *output_pubkey, secp256k1_frost_tweak_cache *tweak_cache, const unsigned char *tweak32) { + return secp256k1_frost_pubkey_tweak_add_internal(ctx, output_pubkey, tweak_cache, tweak32, 1); +} + #endif