5.0 KiB
title, author, date
| title | author | date | |||
|---|---|---|---|---|---|
| FROST in cmp-ecdsa |
|
14-07-2021 |
We've implemented the FROST protocol inside of this repo. This document details the departures we've made from the protocol as specified in the paper, along with the additions we've contributed.
Hedged Deterministic Nonces
When signing, each party needs to generate two random nonces d_i, e_i \in \mathbb{Z}/(q).
Instead of simply sampling them randomly, we instead use a deterministic procedure,
which optionally incorporates randomness:
\begin{aligned}
k &\leftarrow \text{KDF}(s_i) \\
a &\xleftarrow{R} \{0, 1\}^{256} \\
(d_i, e_i) &\leftarrow H_k(\text{SSID} || m || a)
\end{aligned}
\text{KDF} is BLAKE3 in KDF mode, and H_k is BLAKE3 in keyed-hash mode, \text{SSID}
is a unique session identifier incorporating context information, like the protocol,
the curve being used, the participants, etc. m is the message, or message hash, and
s_i is the private share for this participant.
Even if a is not generated randomly, the result nonces cannot be computed, because the hash
depends on knowing the secret share s_i.
Incorporating a random a protects against fault attacks, by making different different
signings of the same message produce different nonces.
It would likely be safe to include the share s_i in the hash directly, instead
of deriving a hashing key. We think that the deriving the key for a keyed hash is more
defensible from the principle that keys should only be used for a single purpose.
Chaining Key
For some purposes, it's useful to have an additional bit of randomness alongside a public key, called a "chaining key". This is useful for key derivation in BIP-32, for example.
We do this by having each party generate a random byte sequence c_i, broadcast
a commitment in round 1, and then decommit in round 2. The final chaining key
is then simply \bigoplus_i c_i.
Taproot compatible signatures
We've implemented a variant of FROST to generate signatures compatible with
Bitcoin Taproot / BIP-340.
The main differences are a specific method for generating the challenge c,
but more importantly, the fact that points are encoded for public keys and signatures
by completely discarding the y coordinate. When decoding the x coordinate
into a full point, the point with even y coordinate is always chosen.
The former difference is easy to accomodate, we generate c as:
\text{SHA-256}(\text{SHA-256}(\text{`BIP0340/challenge'}) ||
\text{SHA-256}(\text{`BIP0340/challenge'}) || x(R) || x(P) || m)
as per BIP-340.
For the point encoding with y coordinates, we need to modify the protocol in a few different places to accomodate this.
Key-Generation
When signing, you conditionally negate your secret key s, so that Y = s \cdot G
has an even y coordinate. You do this by calculating Y, and then negating s if
the y coordinate is odd. In our case, we choose to generate the shared secret so
that it satisfies this property.
Since:
s = \sum_l a_{l0}
Negating s means negating each a_{l0}. If we have a polynomial sharing
of a_{l0} as:
f_l(x) = a_{l0} + \ldots + a_{lt} x^t
then negating the polynomial to get -f_l(x) would provide the correct polynomial
sharing of a_{l0}. Because our secret share s_i is the sum of these polynomials
evaluated at a certain point, negating the share effectively negates each a_{i0},
and thus effectively negates s.
Our modification to key generation is to check if our public key Y has an odd y
coordinate, and if so, negate our own share s_i, as well as each
verification share Y_l := s_l \cdot G.
Note that you could do all of this when signing, instead of at the end of the
key-generation step. You would reconstruct the public key Y using the verification shares
Y_i, and then conditionally negate s as we do above. We find it simpler to integrate
this into the key-generation process.
Regardless, the downside to having to do this is that if a party doesn't respect this normalization process, you'll only realize this when being unable to generate a valid signature later.
Signing
When generating a nonce k for signing, you need to check if the commitment
R = k \cdot G has an even y coordinate, negating k if not. In our case,
we have:
k = \sum_l (d_l + \rho_l e_l)
In order to negate k, it suffices to negate each d_l and e_l.
We modify the signing protocol at step 4. After calculating the commitment
R, we check if it has an odd y coordinate, if so, we negate our own nonces
d_i and e_i, as well as the commitment shares R_l = (d_l + \rho_l e_l) \cdot G
for each participant l.
If a party doesn't participate in this normalization, their response z_l will
fail to validate later.
We also modify this step to calculate the hash in a standardized way.