Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FROST Trusted Dealer #278

Open
wants to merge 10 commits into
base: master
Choose a base branch
from

Conversation

jesseposner
Copy link
Contributor

@jesseposner jesseposner commented Nov 23, 2023

This PR implements a BIP-340 compatible threshold signature system based on FROST (Flexible Round-Optimized Schnorr Threshold Signatures) with a trusted dealer. This PR does not implement distributed key generation (DKG).

FROST Paper

This PR is based upon the FROST paper by Chelsea Komlo and Ian Goldberg and the draft RFC.

FROST Signing BIP

This PR implements the FROST signing BIP.

Prior Work

This PR is patterned on the APIs, and in many instances duplicates code, from the MuSig2 implementation due to their similarities in nonce generation and signing.

@jesseposner jesseposner changed the title Frost trusted dealer FROST Trusted Dealer Nov 23, 2023
Copy link
Collaborator

@real-or-random real-or-random left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some initial comments, more tomorrow

/** Serialize a FROST share
*
* Returns: 1 when the share could be serialized, 0 otherwise
* Args: ctx: a secp256k1 context object
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: see bitcoin-core/secp256k1@aa3dd52 for consistent wording here

* Each participant _must_ have a secure channel with the trusted dealer with
* which they can transmit shares to each other.
*
* A new seed32 _must_ be used for each key generation session. The trusted
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: s/new/fresh

By the way, we need to say similar things for musig sessions. Feel free to steal some sentences/phrasing ideas from bitcoin-core/secp256k1#1479.

Comment on lines 74 to 196
* start a new session to generate a new key, they must NOT REUSE their
* respective seed32 again, but instead generate a new one. It is recommended
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/again// ("reuse again" is saying the same thing twice)

const unsigned char *in32
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);

/** Creates key generation shares
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"key shares" or "key generation" shares?

Comment on lines 68 to 190
* Each participant _must_ have a secure channel with the trusted dealer with
* which they can transmit shares to each other.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, this may be slightly confusing because one could read it as "must have established a secure channel already", but that's of course not necessary. Maybe: "The trusted dealer must transmit shares over secure channels to participants."

secp256k1_sha256_write(&sha, polygen, 16);
secp256k1_sha256_finalize(&sha, polygen);

/* Derive share */
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/share/shares

for (i = 0; i < n_participants; i++) {
secp256k1_scalar share_i, idx;

secp256k1_scalar_clear(&share_i);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is just for nuking memory. Use _scalar_set_int(0) to set to zero. (We should probably have a dedicated function, but we don't have one currently.)

* Args: ctx: pointer to a context object
* Out: shares: pointer to the key generation shares
* pubshares: pointer to the public verification shares
* pk: pointer to the x-only public key
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be an x-only key? I'm not entirely sure, I would need to think about it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function conditionally inverts the shares to guarantee that the private key will match the x-only key.

Since the primary use-case will be on-chain signing, I think it's a useful simplification to take care of the negation during share generation and makes it easier to reason about in secp256k1_frost_pubkey_get and secp256k1_frost_pubkey_tweak.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Upon further reflection, I'm realizing it's not a useful simplification because in practice the output of the DKG will typically be further tweaked by a BIP32 tweak and then a taproot tweak, rather than used directly.

So I now think it would be better to have the DKG output a 33-byte key rather than an x-only key.

Copy link
Contributor Author

@jesseposner jesseposner Aug 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've taken a stab at implementing this change, however, it requires tracking more state during signing.

Currently, nonce_process takes an xonly agg_pk and optionally a tweak_cache and returns a session. partial_sign takes a session and optionally a tweak_cache.

partial_sign only needs to handle negation logic if a tweak_cache is present because the DKG outputs an x-only key. If we change the DKG to output a 33-byte key, then nonce_process will need to take a 33-byte agg_pk and it will need to save a parity bit, serialized as a byte, in the session, so that partial_sign can handle the negation logic for when a tweak_cache is not present.

So while I don't think there's any reason for the protocol to output a 33-byte key, it seems useful to have the implementation do so, because it avoids a serialized byte in the session and I don't see a downside.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to compare this approach to the one taken in the signing BIP. The BIP proposes that the pubshares be saved to the session and the 33 byte public key is derived from the pubshares.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps the best reason to not output an xonly key from the DKG is that we don't want to encourage FROST DKG outputs being used directly on-chain without an unspendable script path because unlike MuSig, FROST does not randomize the group public key: https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#cite_note-23

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The signing BIP now uses signer public shares to initialize the tweak context (name to be updated) rather than the group public key. Hence, it no longer depends on whether the group pubkey (produced by keygen) is plain or x-only.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An alternative design choice here is for trusted_gen to always output tweak_cache and optionally group_pk. This would be similar to how musig_pubkey_agg outputs keyagg_cache and optionally agg_pk.

@real-or-random
Copy link
Collaborator

real-or-random commented Apr 18, 2024

In general, I think it would be nice to get this rebase this. (Should be mostly trivial.) Also, CMake support is missing because we have this now for all other -zkp modules (#288 ), but that's currently not essential right now.

ARG_CHECK(n_pubnonces > 1);
ARG_CHECK(my_id != 0);
for (i = 0; i < n_pubnonces; i++) {
ARG_CHECK(ids[i] != 0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we also ARG_CHECK for 1 <= ids[i] <= max_participant?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function doesn't currently take max_participant as an input, and I'm not sure if it's worth requiring the extra state to perform this check.

const unsigned char *msg32,
const secp256k1_xonly_pubkey *agg_pk,
const unsigned char *extra_input32
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can also add pubshare as an input argument. MuSig2 hashes the individual pubkey when generating the nonces, but we don't need to make pubshare a mandatory argument like MuSig2 does (more info).

How do we decide the args supplied to the hash function for generating a nonce? Should we throw in all available signer information into the hash function? For instance, can we include the participant identifier too?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related issue: siv2r/bip-frost-signing#15, which suggests using tweak_cache instead of agg_pk as the input arg.

configure.ac Outdated
@@ -470,6 +475,14 @@ if test x"$enable_module_ecdsa_adaptor" = x"yes"; then
SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_ECDSA_ADAPTOR=1"
fi

if test x"$enable_module_frost" = x"yes"; then
if test x"$enable_module_schnorrsig" = x"no"; then
AC_MSG_ERROR([Module dependency error: You have disabled the schnorrsig module explicitly, but it is required by the musig module.])
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

%s/musig/frost

configure.ac Outdated
Comment on lines 595 to 597
if test x"$enable_module_frost" = x"yes"; then
AC_MSG_ERROR([FROST module is experimental. Use --enable-experimental to allow.])
fi
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: when we execute ./configure --enable-module-frost, the following error appears:

configure: error: MuSig module is experimental. Use --enable-experimental to allow.

instead of:

configure: error: FROST module is experimental. Use --enable-experimental to allow.

To resolve this issue, we should reposition the FROST module check before the MuSig experimental module check (lines 486-488).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you try again? I wasn't able to reproduce the issue.

When I run ./configure --enable-module-frost I get configure: error: FROST module is experimental. Use --enable-experimental to allow..

secp256k1_sha256_finalize(&sha, polygen);

/* Derive share */
/* See draft-irtf-cfrg-frost-08#appendix-C.1 */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: we can update this to refer to the completed RFC: https://datatracker.ietf.org/doc/rfc9591/

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines 130 to 131
/* TODO: this doesn't have the same sidechannel resistance as the BIP340
* nonce function because the seckey feeds directly into SHA. */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. We must XOR key32 and session_id (in const-time) before feeding them to SHA256

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to bring these changes over from MuSig: 206017d

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

src/group.h Outdated
Comment on lines 205 to 207
static void secp256k1_point_save_ext(unsigned char *data, secp256k1_ge *ge);

static void secp256k1_point_load_ext(secp256k1_ge *ge, const unsigned char *data);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The libsecp repo defines these functions using different names:

  • point_save_ext -> ge_to_bytes_ext
  • point_load_ext -> ge_from_bytes_ext

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

* 3. Avoid copying (or serializing) the secnonce. This reduces the possibility
* that it is used more than once for signing.
*
* Remember that nonce reuse will leak the secret key!
Copy link
Contributor

@siv2r siv2r Aug 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be more precise to say "will leak the agg_share" instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

secp256k1_sha256 sha;
unsigned char seed[32];
unsigned char i;
enum { n_extra_in = 4 };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
enum { n_extra_in = 4 };
enum { N_EXTRA_IN = 4 };

Comment on lines 140 to 145
for (i = 0; i < n_extra_in; i++) {
unsigned char len;
if (extra_in[i] != NULL) {
len = 32;
secp256k1_sha256_write(&sha, &len, 1);
secp256k1_sha256_write(&sha, extra_in[i], 32);
Copy link
Contributor

@siv2r siv2r Aug 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this approach to hashing optional arguments—it’s cleaner than musig’s approach of using a helper function. However, there's a minor issue: the signing BIP encodes the message length as 8 bytes, but here we use 1 byte.

As stated in the BIP's noncegen:

  • If the optional argument m is not present:
    • Let m_prefixed = bytes(1, 0)
  • Else:
    • Let m_prefixed = bytes(1, 1) || bytes(8, len(m)) || m

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I switched over to using the MuSig approach: 35f453d

return 1;
}

static void secp256k1_nonce_function_frost(secp256k1_scalar *k, const unsigned char *session_id, const unsigned char *msg32, const unsigned char *key32, const unsigned char *pk32, const unsigned char *extra_input32) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a note for the future: when writing tests, we should skip two noncegen test vectors—(1) empty message and (2) 38-byte message—since our nonce generation currently only supports 32-byte messages.

Comment on lines 151 to 155
secp256k1_sha256_finalize(&sha, seed);

for (i = 0; i < 2; i++) {
unsigned char buf[32];
secp256k1_sha256_initialize(&sha);
Copy link
Contributor

@siv2r siv2r Aug 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method of hashing arguments in noncegen deviates from the BIP. I believe it currently computes the nonces as

$$k_i = \mathrm{H}_{\text{no-tag}}(\mathrm{H}_{\text{FROST/nonce}}(\text{optional args}) \;||\; i - 1)$$

but the BIP computes them as

$$k_i = \mathrm{H}_{\text{FROST/nonce}}(\text{optional args} \;||\; i - 1)$$

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return 1;
}

int secp256k1_frost_nonce_process(const secp256k1_context* ctx, secp256k1_frost_session *session, const secp256k1_frost_pubnonce * const* pubnonces, size_t n_pubnonces, const unsigned char *msg32, const secp256k1_xonly_pubkey *pk, size_t my_id, const size_t *ids, const secp256k1_frost_tweak_cache *tweak_cache, const secp256k1_pubkey *adaptor) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function follows the nonce aggregation technique from FROST1, but the BIP uses FROST3. We may need to split this function into two:

  1. frost_nonce_agg - computes the aggnonce: $\widetilde{R_1} || \widetilde{R_2}$ where,
    $\widetilde{R_1} = \sum \limits_{i=1}^{t} R_{i, 1}$ and $\widetilde{R_2} = \sum \limits_{i=1}^{t} R_{i, 2}$
  2. frost_nonce_process - computes the final nonce: $R_{\text{fin}} = \widetilde{R_1} + b \cdot \widetilde{R_2}$

Musig’s implementation follows the same technique

the generator (by simply adding the generator to one of the
malicious nonces), and this does not change the winning condition
of the EUF-CMA game. */
aggnonce_pt[i] = secp256k1_ge_const_g;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe aggnonce_pt[i] is allowed to be infinity. The NonceAgg specification serializes them using cybtes_ext, so we can use ge_to_bytes_ext here.

Setting the nonce to the generator point only occurs with the final nonce.


/* TODO: consider updating to frost-08 to address maleability at the cost of performance */
/* See https://github.com/cfrg/draft-irtf-cfrg-frost/pull/217 */
static int secp256k1_frost_compute_noncehash(const secp256k1_context* ctx, unsigned char *noncehash, const unsigned char *msg, const secp256k1_frost_pubnonce * const* pubnonces, size_t n_pubnonces, const unsigned char *pk32, const size_t *ids) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FROST3 computes the nonce coefficient as $b = \mathrm{H}_{\text{FROST/noncecoef}}(T || \widetilde{R_1} || \widetilde{R_2} || \widetilde{X} || msg)$, where $T$ is the set of signer ids.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can remove the ctx object from the function args.

return 1;
}

static int secp256k1_frost_nonce_process_internal(const secp256k1_context* ctx, int *fin_nonce_parity, unsigned char *fin_nonce, secp256k1_scalar *b, secp256k1_gej *aggnoncej, const unsigned char *msg, const secp256k1_frost_pubnonce * const* pubnonces, size_t n_pubnonces, const unsigned char *pk32, const size_t *ids) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can remove the ctx object from the function args

secp256k1_gej_add_ge_var(&fin_nonce_ptj, &fin_nonce_ptj, &aggnonce[0], NULL);
secp256k1_ge_set_gej(&fin_nonce_pt, &fin_nonce_ptj);

if (secp256k1_ge_is_infinity(&fin_nonce_pt)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The BIP sets the final nonce to the generator point if it’s infinity.

secp256k1_scalar_add(&session_i.s_part, &session_i.s_part, &e_tmp);
}
}
/* Update the challenge by multiplying the Lagrange coefficient to prepare
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hadn’t considered this option when writing the BIP. So, right now, it computes $\lambda_{i, T}$ during Sign, not in GetSessionValue. Happy to update the BIP if this approach is better.

Comment on lines 378 to 379
size_t my_id,
const size_t *ids,
Copy link
Contributor

@siv2r siv2r Aug 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the documentation, we should probably mention that [1] the ids array must have unique elements, and [2] my_id must be included in ids. Otherwise, Lagrange interpolation will return incorrect values.

We could also implement ARG_CHECKs for these, but I'm not sure if it's worth the effort.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In BIP, the DeriveInterpolatingValue function fails if both these conditions aren't met.

* Args: ctx: pointer to a context object
* In: pre_sig64: 64-byte pre-signature
* msg32: the 32-byte message being verified
* pubkey: pointer to an x-only public key to verify with
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* pubkey: pointer to an x-only public key to verify with
* agg_pk: pointer to an x-only public key to verify with

* nonce_parity: the output of `frost_nonce_parity` called with the
* session used for producing the pre-signature
*/
SECP256K1_API int secp256k1_frost_verify_adaptor(
Copy link
Contributor

@siv2r siv2r Aug 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just pointing out that there's another possible approach here. In single signer Schnorr adaptor, we use extract_adaptor_point API instead of verify_adaptormore details. Interestingly, MuSig doesn’t include either extract_adaptor_point or verify_adaptor (not sure why).

Comment on lines 553 to 555
if (secp256k1_fe_is_odd(&cache_i.pk.y) != cache_i.parity_acc) {
secp256k1_scalar_negate(&sk, &sk);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can use this comment here (taken from musig)

    /* Negate sk if secp256k1_fe_is_odd(&cache_i.pk.y)) XOR cache_i.parity_acc.
     * This corresponds to the line "Let d = g⋅gacc⋅d' mod n" in the
     * specification. */

ARG_CHECK(sig64 != NULL);
ARG_CHECK(session != NULL);
ARG_CHECK(partial_sigs != NULL);
ARG_CHECK(n_sigs > 0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ARG_CHECK(n_sigs > 0);
ARG_CHECK(n_sigs > 1);

Should we do this instead?
The frost_nonce_process currently has ARG_CHECK(n_pubnonce > 1). We might want to use the same value in both functions, either 1 or 0.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In musig's example, every function (except main) is static

examples/frost.c Outdated
}
/* Mark signer as assigned */
pubnonces[i] = &signer[signer_id].pubnonce;
/* pubkeys[i] = &signer[signer_id].pubkey; */
Copy link
Contributor

@siv2r siv2r Aug 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this comment was meant to be removed

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#278 (comment) applies here too.

Comment on lines +733 to +873
for (i = 0; i < COUNT; i++) {
frost_multi_hop_lock_tests();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can move this function call to the previous loop

CHECK(secp256k1_gej_is_infinity(&summed_nonces[1]));
}

int frost_memcmp_and_randomize(unsigned char *value, const unsigned char *expected, size_t len) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same function is also used in MuSig's tests, so we can make it a helper function in src/testutil.h.

Comment on lines +592 to +558
/* Compute "effective" nonce rj = aggnonce[0] + b*aggnonce[1] */
/* TODO: use multiexp to compute -s*G + e*pubshare + aggnonce[0] + b*aggnonce[1] */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to use pubnonce instead of aggnonce: #296

jesseposner added a commit to jesseposner/secp256k1-zkp that referenced this pull request Aug 28, 2024
jesseposner added a commit to jesseposner/secp256k1-zkp that referenced this pull request Aug 28, 2024
jesseposner added a commit to jesseposner/secp256k1-zkp that referenced this pull request Aug 29, 2024
Based on:
57de68994ae18d20b0b6e1a9e4531c3d88b5ec81 and 3f9bb4d868a2a27caacdaf19b08ce91ce73c1fb4

Responds to:
BlockstreamResearch#278 (comment)
jesseposner added a commit to jesseposner/secp256k1-zkp that referenced this pull request Aug 29, 2024
This commit adds the foundational configuration and building scripts
and an initial structure for the project.
This commit adds trusted share generation, as well as share
serialization and parsing.
This commit adds share verification, as well as computation of public
verification shares.
This commits add BIP-341 ("Taproot") and BIP-32 ("ordinary") public key
tweaking.
This commits adds nonce generation, as well as serialization and
parsing.
This commit adds nonce aggregation, as well as adaptor signatures.
This commit adds signature generation and aggregation, as well as
partial signature serialization and parsing.
@jesseposner jesseposner force-pushed the frost-trusted-dealer branch 3 times, most recently from d53f97e to abb2ee3 Compare September 10, 2024 04:22
This commit adds an example file to demonstrate how to use the module.
Add api tests, nonce tests, tweak tests, sha256 tag tests, and constant
time tests.
This commit adds a documentation file with detailed instructions for how
to use the module properly.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants