frost trusted dealer: key tweaking
This commits add BIP-341 ("Taproot") and BIP-32 ("ordinary") public key tweaking.
This commit is contained in:
parent
9b852191de
commit
5368c81a3c
@ -16,6 +16,9 @@ extern "C" {
|
|||||||
* Threshold Signatures (FROST) by Chelsea Komlo and Ian Goldberg
|
* Threshold Signatures (FROST) by Chelsea Komlo and Ian Goldberg
|
||||||
* (https://crysp.uwaterloo.ca/software/frost/).
|
* (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
|
* 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.
|
* 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.
|
* 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.
|
/** Opaque data structure that holds a signer's _secret_ share.
|
||||||
*
|
*
|
||||||
* Guaranteed to be 36 bytes in size. Serialized and parsed with
|
* 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
|
size_t n_participants
|
||||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5);
|
) 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
|
/** Starts a signing session by generating a nonce
|
||||||
*
|
*
|
||||||
* This function outputs a secret nonce that will be required for signing and a
|
* This function outputs a secret nonce that will be required for signing and a
|
||||||
|
@ -10,8 +10,15 @@
|
|||||||
#include "../../../include/secp256k1.h"
|
#include "../../../include/secp256k1.h"
|
||||||
#include "../../../include/secp256k1_frost.h"
|
#include "../../../include/secp256k1_frost.h"
|
||||||
|
|
||||||
|
#include "../../group.h"
|
||||||
#include "../../scalar.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);
|
static int secp256k1_frost_share_load(const secp256k1_context* ctx, secp256k1_scalar *s, const secp256k1_frost_share* share);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -20,6 +20,39 @@
|
|||||||
#include "../../hash.h"
|
#include "../../hash.h"
|
||||||
#include "../../scalar.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 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) {
|
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;
|
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
|
#endif
|
||||||
|
Loading…
x
Reference in New Issue
Block a user