-
Notifications
You must be signed in to change notification settings - Fork 209
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 #138
base: master
Are you sure you want to change the base?
FROST #138
Conversation
@jesseposner Interesting work! I am currently thinking of a system where a large group of people would maintain ownership over a pool of funds, for example, within a single Bitcoin wallet. The FROST scheme looks like it could be a primitive in this scheme, especially if combined with an accountability protocol to detect/exclude malicious signers. Would this PR be ready for some preliminary experiments in a P2P setting? |
@devos50 The PR is currently in a very early state and isn't that useful yet, in particular, it doesn't yet have support for signing or generating nonces. However, I've made significant progress in my local branch and I will be pushing substantial changes to the PR by the end of the month that will have nonce generation and signing and should be sufficient for experimentation. |
@jesseposner thanks for your response! I will wait for your changes then and try to build something around it later when the PR is more feature-complete 👍 (I'm going on holiday tomorrow so no need to rush). |
Here's another BIP340 FROST implementation (in Go): I think it would be nice to have a chat with them. |
By the way, there's a new paper about FROST that has a nice pseudocode in Figure 10: https://eprint.iacr.org/2021/1375.pdf |
Just curious, are there any significant changes to the protocol itself (ignoring implementation details -- of course the line is fuzzy)? |
@real-or-random I'll have a full write-up ready very soon, but the general idea is that I'm using a method for combining MuSig and FROST that provides the following advantages: (1) MuSig keys can be converted to FROST keys without changing the aggregate public key, (2) a "broadcast channel" is implemented using MuSig to sign a hashed list of coefficient commitments, and (3) MuSig APIs are utilized ( The FROST protocol is modified such that the first step is to generate a MuSig key. From there, the participants generate random polynomial coefficients, however, for their first coefficient the participants use their respective individual MuSig private key multiplied by the key aggregation coefficient. This results in the FROST and MuSig aggregate public key being the same, because the FROST aggregate public key is the sum of the commitments to each participant's first polynomial coefficient. This also means that the participants no longer need to distribute a proof of knowledge of their first coefficient, as specified by FROST, because the rogue key attack is prevented via MuSig and the key aggregation coefficients. Then, when the participants distribute their shares and coefficient commitments, the FROST protocol is modified such that they also distribute a MuSig nonce commitment pair. When they have received coefficient commitments from all the other participants, they generate a hash of the list (i.e. the At that point, each participant has a FROST share that they can use for threshold signing, however, they use MuSig nonces (MuSig nonce generation and FROST nonce generation are nearly identical: in both cases the protocols use a pair of nonces and nonce commitments that are combined with a binding value). The example file currently implements this method of key generation, nonce generation, and signing: https://github.com/ElementsProject/secp256k1-zkp/blob/36eba62b3b5450f0acde3459cf7600af2116bc32/examples/frost.c. If we want to use the more standard FROST protocol, the code changes required will be relatively modest (e.g. remove keyagg coefficient from share generation, add a proof of knowledge in share generation, create a |
I had a closer look at the modified KeyGen (the picture in the first comment) and here are few comments:
|
@real-or-random Thank you for the comments!
Reuse is potentially helpful if you have funds secured with a MuSig setup and later decide you'd like to convert to a threshold setup without needing to move the funds. In practice I'm not sure how useful or desirable this actually is and I'm open to the idea that the security downsides outweigh the benefits. Even if that's the case, we can still use MuSig for the broadcast channel (assuming we want to do that), and change the API such that it doesn't allow for converting a pre-existing MuSig key.
I agree that we need a security proof for the modified protocol if we think that's worth pursuing. However, the MuSig partial signatures are also proofs of knowledge, so I think the modified protocol still provides that.
The MuSig partial signatures are not generated until every participant has received their shares, verified their shares, and aggregated their shares. So when a participant receives partial signatures from every other participant, they know that each participant has the shares required to sign for the aggregate public key assuming that all participants verified their shares against the same commitments. This final assumption is validated when the partial signatures are aggregated and the aggregate is verified as a valid signature. So at that point, I believe the participants are left in that same final state you described at the end, where they are potentially uncertain (because they don't know if the other participants were able to verify the aggregate signature), however signing will be possible assuming they don't timeout and delete.
That looks very useful, thanks for the suggestion!
Great point! |
Hm yes, I tend to believe that converting from a MuSig pubkey to a FROST pubkey is rare enough that it doesn't matter to change the pubkey.
The partial sigs is one part where I'm not sure. A single partial sig is not a proof of knowledge because the hashed nonce is not the partial nonce of the participant. A maybe surprising property is that partial sigs during a MuSig2 run can be forged -- just not all partial signatures of one run because this would imply a real forgery. Now in this case we get all partial sigs (and a full sig) but one would need to see if this really means that all keys can be extracted. I never thought about this to be honest.
Ok yes, I think you're right. |
examples/frost.c
Outdated
if (!secp256k1_musig_nonce_agg(ctx, &agg_pubnonce, pubnonces, THRESHOLD)) { | ||
return 0; | ||
} | ||
if (!secp256k1_musig_nonce_process(ctx, &session, &agg_pubnonce, msg32, &cache, NULL)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The combination of using MuSig2-style nonce aggregation and reusing MuSig2's way of computing the coefficient for the nonce ("b" in the notation of MuSig2 paper) seems to make this vulnerable to Wagner/BLLOR-style attacks (see MuSig2 paper) if the attacker can choose the set of t participants after seeing an honest signer's nonce pair. Letting the attacker choose the set of participants will give the attacker some control over the Lagrange coefficient, which has a similar effect as giving the attacker control over the Fiat-Shamir challenge, because the two values are simply multiplied to each other.. I say "similar" because the Lagrange coefficient is structured and not random but I don't except that this makes an attack significantly harder or easier.
The fix is to include the set of participants in the derivation of b.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, that's a great point. This is fixed in changes I will be pushing shortly that make nonce generation conform to the latest FROST spec (https://datatracker.ietf.org/doc/draft-irtf-cfrg-frost/).
Awsome work @jesseposner, thank you. Do you consider stress testing your code with 10M users? |
Thanks @synctext! I'm excited to see that your students were able to leverage this code for their project, I will check it out. Please be careful, the code is still early and has not yet been reviewed and should not be considered secure software. Great suggestion regarding the stress testing, I will add it to the to-do list. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jesseposner I had a shallow look at your PR. In particular, I like the implementation of the vss_hashes
because it allows to see when key generation has worked. Ideally, we would also be able to assign blame if the vss_hashes
are not identical and then resolve the issue through some other process.
As far as I can see, there are no proofs of knowledge involved in key aggregation and, instead, MuSig-aggregation is assumed make PoKs unnecessary. I think this would require a security proof/argument.
Agree with @real-or-random that we should try to get rid of signer indices entirely if possible.
I pushed a branch with a few fixup! commits and CI integration. Feel free to cherry-pick: https://github.com/jonasnick/secp256k1-zkp/commits/frost-jn.
Thanks for the comments @jonasnick!
The By moving the PoK to the second round and including the
Great point, I added this to the to-do list.
Thanks, will do! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, clever idea to use this signature as the PoK. I pushed two more commits to my branch. Feel free to cherry-pick if you agree that they're useful.
I think one open todo that may be useful to keep in the OP is:
- decide on whether to use MuSig key aggregation
Btw, I mentioned earlier that
Ideally, we would also be able to assign blame if the vss_hashes are not identical and then resolve the issue through some other process.
It seems like this API is already close (requires share_agg(...)
to indicate which share failed to verify) to providing the possibility to do something like this.
- When distributing shares, signers sign vss_commitment and share, and send the signature to the along with the vss_commitment.
- The receiving signer does vss verification of the share. If it fails, the share, the vss_commitment and the signature can be used to complain. Perhaps it makes sense to make the
vss_verify
function in my branch public because it would allow a third party with minimal information about the key generation session to verify the complaint. - If the
vss_hash
turns out to not be identical, every signer submits all received vss_commitments and their signatures to the complaint handler, which should be sufficient to identify disruptive signers.
One problem is that there's no agreed-upon session identifer that is covered by the signature, so signatures are transferrable between sessions. A malicious signer could send a received vss_commitment and signature from a different session to the complaint handler to blame an innocent signer that was present in two keygen sessions.
done
Here's an idea for how we might incorporate a session ID by building on what you described:
|
Regarding the issue of identifying dishonest signers in the key generation phase, @real-or-random rightfully points out that transcript + signatures only helps if the signers already agree on the individual public keys. If there's disagreement, the signature is worthless. Establishing agreement is difficult. I'm not sure how much sense it makes in practice to simply assume that there is already agreement on the public keys. |
@jonasnick Perhaps we can use the key pair provided to |
54120ad
to
28fa632
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The session IDs prevent data from another session being used to blame an innocent signer.
Let me try to expand on the argument: If malicious signer A complains that innocent signer B sent a bad vss_comm that resulted in A having the wrong vss_hash, A must provide sig_B(vss_comm, share, ID) as well as sig_B(vss_hash, ID). If these signatures are from a different session, then the vss_hash differs and therefore B can prove its innocence by showing that it computed the vss_hash correctly. As an aside, the vss_hash may be the same across sessions because they are derived deterministically from the inputs to keygen.
Cool idea. But it's really hard to convince yourself that it works in any case and risks inventing complicated machinery that turns out to be broken. I think this needs more formal treatment. In any case, blaming - even with your idea - does not seem to require API changes. Blaming seems to be something that we can focus on later.
This commits add BIP-341 ("Taproot") and BIP-32 ("ordinary") public key tweaking.
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.
This commit adds an example file to demonstrate how to use the module.
a6e1375
to
3891388
Compare
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.
5937a43
to
d9403a4
Compare
b607a8c
to
2c23466
Compare
*/ | ||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_pubkey_get( | ||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_pubkey_gen( | ||
const secp256k1_context *ctx, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: frost_pubkey_gen
-> frost_keygen_cache_init
?
The BIP calls it KeygenCtxInit
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed my mind. Libsecp APIs don't seem to use this init
style. I'll have the BIP call it KeygenCtx instead.
7a29ddc
to
0135f9f
Compare
This PR implements a BIP-340 compatible threshold signature system based on FROST (Flexible Round-Optimized Schnorr Threshold Signatures).
TODO
FROST Paper
This PR is based upon the FROST paper by Chelsea Komlo and Ian Goldberg and the draft RFC.
Python Implementation [WIP]
https://github.com/jesseposner/FROST-BIP340
Prior Work
@apoelstra worked on a threshold implementation in #46 and this PR builds on the techniques introduced there.
This PR is patterned on the APIs, and in many instances duplicates code, from the MuSig implementation due to their similarities in nonce generation and signing.